├── .gitignore
├── phpspec.yml.dist
├── Process
├── Coordinator
│ ├── InvalidArgumentException.php
│ ├── CoordinatorInterface.php
│ └── Coordinator.php
├── Step
│ ├── AbstractContainerAwareStep.php
│ ├── ActionResult.php
│ ├── AbstractStep.php
│ ├── AbstractControllerStep.php
│ └── StepInterface.php
├── Scenario
│ └── ProcessScenarioInterface.php
├── Builder
│ ├── ProcessBuilderInterface.php
│ └── ProcessBuilder.php
├── Context
│ ├── ProcessContextInterface.php
│ └── ProcessContext.php
├── ProcessInterface.php
└── Process.php
├── CHANGELOG.md
├── Validator
├── ProcessValidatorException.php
├── ProcessValidatorInterface.php
└── ProcessValidator.php
├── Resources
├── config
│ ├── routing.yml
│ └── container
│ │ ├── validators.xml
│ │ ├── builders.xml
│ │ ├── contexts.xml
│ │ ├── controllers.xml
│ │ ├── coordinators.xml
│ │ └── storages.xml
└── meta
│ └── LICENSE
├── spec
├── Process
│ ├── Coordinator
│ │ ├── InvalidArgumentExceptionSpec.php
│ │ └── CoordinatorSpec.php
│ ├── Step
│ │ └── ActionResultSpec.php
│ ├── Builder
│ │ └── ProcessBuilderSpec.php
│ ├── ProcessSpec.php
│ └── Context
│ │ └── ProcessContextSpec.php
├── Validator
│ ├── ProcessValidatorExceptionSpec.php
│ └── ProcessValidatorSpec.php
├── Storage
│ ├── SessionFlowsBagSpec.php
│ └── SessionStorageSpec.php
└── DependencyInjection
│ └── Compiler
│ ├── RegisterSessionBagsPassSpec.php
│ ├── RegisterStepsPassSpec.php
│ └── RegisterScenariosPassSpec.php
├── Storage
├── AbstractStorage.php
├── SessionFlowsBag.php
├── StorageInterface.php
└── SessionStorage.php
├── DependencyInjection
├── Compiler
│ ├── RegisterSessionBagsPass.php
│ ├── RegisterStepsPass.php
│ └── RegisterScenariosPass.php
├── Configuration.php
└── SyliusFlowExtension.php
├── SyliusFlowBundle.php
├── composer.json
├── Controller
└── ProcessController.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | bin/
3 |
4 | composer.phar
5 | composer.lock
6 |
--------------------------------------------------------------------------------
/phpspec.yml.dist:
--------------------------------------------------------------------------------
1 | suites:
2 | main:
3 | namespace: Sylius\Bundle\FlowBundle
4 | psr4_prefix: Sylius\Bundle\FlowBundle
5 | src_path: .
6 |
--------------------------------------------------------------------------------
/Process/Coordinator/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\Process\Coordinator\InvalidArgumentException');
21 | }
22 |
23 | function it_is_a_exception()
24 | {
25 | $this->shouldHaveType(\Exception::class);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Process/Step/AbstractContainerAwareStep.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | abstract class AbstractContainerAwareStep extends AbstractStep implements ContainerAwareInterface
24 | {
25 | use ContainerAwareTrait;
26 | }
27 |
--------------------------------------------------------------------------------
/Storage/AbstractStorage.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | abstract class AbstractStorage implements StorageInterface
20 | {
21 | /**
22 | * Storage domain.
23 | *
24 | * @var string
25 | */
26 | protected $domain;
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | public function initialize($domain)
32 | {
33 | $this->domain = $domain;
34 |
35 | return $this;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/spec/Process/Step/ActionResultSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('step_name');
21 | }
22 |
23 | function it_is_initializable()
24 | {
25 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Process\Step\ActionResult');
26 | }
27 |
28 | function it_knows_the_next_step()
29 | {
30 | $this->getNextStepName()->shouldReturn('step_name');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Process/Step/ActionResult.php:
--------------------------------------------------------------------------------
1 | stepName = $stepName;
25 | }
26 |
27 | /**
28 | * @return string|null The name of the next step.
29 | */
30 | public function getNextStepName()
31 | {
32 | return $this->stepName;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/spec/Validator/ProcessValidatorExceptionSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith(100);
22 | }
23 |
24 | function it_is_initializable()
25 | {
26 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Validator\ProcessValidatorException');
27 | }
28 |
29 | function it_is_http_exception()
30 | {
31 | $this->shouldHaveType(HttpException::class);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Process/Scenario/ProcessScenarioInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | interface ProcessScenarioInterface
25 | {
26 | /**
27 | * Builds the whole process.
28 | *
29 | * @param ProcessBuilderInterface $builder
30 | */
31 | public function build(ProcessBuilderInterface $builder);
32 | }
33 |
--------------------------------------------------------------------------------
/spec/Storage/SessionFlowsBagSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\Storage\SessionFlowsBag');
23 | }
24 |
25 | function it_is_a_namespace_attribute_bag()
26 | {
27 | $this->shouldHaveType(NamespacedAttributeBag::class);
28 | }
29 |
30 | function it_has_a_name()
31 | {
32 | $this->getName()->shouldReturn(SessionFlowsBag::NAME);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Storage/SessionFlowsBag.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class SessionFlowsBag extends NamespacedAttributeBag
22 | {
23 | const STORAGE_KEY = '_sylius_flow_bag';
24 | const NAME = '_sylius_flow_bag';
25 |
26 | /**
27 | * Constructor.
28 | */
29 | public function __construct()
30 | {
31 | parent::__construct(self::STORAGE_KEY);
32 | }
33 |
34 | /**
35 | * {@inheritdoc}
36 | */
37 | public function getName()
38 | {
39 | return self::NAME;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Resources/config/container/validators.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Validator\ProcessValidator
22 |
23 |
24 |
25 |
26 | An error occurred.
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Resources/config/container/builders.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Process\Builder\ProcessBuilder
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/Resources/config/container/contexts.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Process\Context\ProcessContext
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/RegisterSessionBagsPass.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class RegisterSessionBagsPass implements CompilerPassInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function process(ContainerBuilder $container)
29 | {
30 | $session = $container->getDefinition('session');
31 | $session->addMethodCall('registerBag', [new Reference('sylius.process_storage.session.bag')]);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Resources/config/container/controllers.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Controller\ProcessController
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Resources/meta/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2016 Paweł Jędrzejewski
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Resources/config/container/coordinators.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Process\Coordinator\Coordinator
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/SyliusFlowBundle.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class SyliusFlowBundle extends Bundle
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function build(ContainerBuilder $container)
31 | {
32 | $container->addCompilerPass(new RegisterScenariosPass());
33 | $container->addCompilerPass(new RegisterStepsPass());
34 | $container->addCompilerPass(new RegisterSessionBagsPass());
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/RegisterStepsPass.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class RegisterStepsPass implements CompilerPassInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function process(ContainerBuilder $container)
29 | {
30 | $processBuilder = $container->getDefinition('sylius.process.builder');
31 |
32 | foreach ($container->findTaggedServiceIds('sylius.process.step') as $id => $attributes) {
33 | $processBuilder->addMethodCall('registerStep', [$attributes[0]['alias'], new Reference($id)]);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/spec/Process/Coordinator/CoordinatorSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($router, $builder, $context);
25 | }
26 |
27 | function it_is_initializable()
28 | {
29 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Process\Coordinator\Coordinator');
30 | }
31 |
32 | function it_is_process_builder()
33 | {
34 | $this->shouldImplement(CoordinatorInterface::class);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/DependencyInjection/Compiler/RegisterScenariosPass.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class RegisterScenariosPass implements CompilerPassInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | public function process(ContainerBuilder $container)
29 | {
30 | $coordinator = $container->getDefinition('sylius.process.coordinator');
31 |
32 | foreach ($container->findTaggedServiceIds('sylius.process.scenario') as $id => $attributes) {
33 | $coordinator->addMethodCall('registerScenario', [$attributes[0]['alias'], new Reference($id)]);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Resources/config/container/storages.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
19 |
20 |
21 | Sylius\Bundle\FlowBundle\Storage\SessionStorage
22 | Sylius\Bundle\FlowBundle\Storage\SessionFlowsBag
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/spec/DependencyInjection/Compiler/RegisterSessionBagsPassSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\DependencyInjection\Compiler\RegisterSessionBagsPass');
25 | }
26 |
27 | function it_is_compiler_pass()
28 | {
29 | $this->shouldImplement(CompilerPassInterface::class);
30 | }
31 |
32 | function it_processes(ContainerBuilder $container, Definition $session)
33 | {
34 | $container->getDefinition('session')->shouldBeCalled()->willreturn($session);
35 | $session->addMethodCall('registerBag', Argument::type('array'))->shouldBeCalled();
36 |
37 | $this->process($container);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class Configuration implements ConfigurationInterface
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function getConfigTreeBuilder()
31 | {
32 | $treeBuilder = new TreeBuilder();
33 | $rootNode = $treeBuilder->root('sylius_flow');
34 |
35 | $rootNode
36 | ->addDefaultsIfNotSet()
37 | ->children()
38 | ->scalarNode('storage')
39 | ->defaultValue('sylius.process_storage.session')
40 | ->cannotBeEmpty()
41 | ->end()
42 | ->end()
43 | ;
44 |
45 | return $treeBuilder;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sylius/flow-bundle",
3 | "type": "symfony-bundle",
4 | "description": "Multiple action setups for Symfony2, build your checkouts/installers or whatever needs more than one step to complete.",
5 | "keywords": ["checkout", "flow", "installation", "steps"],
6 | "homepage": "http://sylius.org",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Paweł Jędrzejewski",
11 | "homepage": "http://pjedrzejewski.com"
12 | },
13 | {
14 | "name": "Sylius project",
15 | "homepage": "http://sylius.org"
16 | },
17 | {
18 | "name": "Community contributions",
19 | "homepage": "http://github.com/Sylius/Sylius/contributors"
20 | }
21 | ],
22 | "require": {
23 | "php": "^5.6|^7.0",
24 |
25 | "symfony/framework-bundle": "^2.8"
26 | },
27 | "require-dev": {
28 | "phpspec/phpspec": "^3.0",
29 | "symfony/yaml": "~2.1"
30 | },
31 | "config": {
32 | "bin-dir": "bin"
33 | },
34 | "autoload": {
35 | "psr-4": { "Sylius\\Bundle\\FlowBundle\\": "" }
36 | },
37 | "autoload-dev": {
38 | "psr-4": { "Sylius\\Bundle\\FlowBundle\\Tests\\": "Tests/" }
39 | },
40 | "minimum-stability": "dev",
41 | "prefer-stable": true,
42 | "repositories": [
43 | {
44 | "type": "path",
45 | "url": "../../*/*"
46 | }
47 | ],
48 | "extra": {
49 | "branch-alias": {
50 | "dev-master": "1.0-dev"
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/DependencyInjection/SyliusFlowExtension.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class SyliusFlowExtension extends Extension
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function load(array $config, ContainerBuilder $container)
31 | {
32 | $config = $this->processConfiguration($this->getConfiguration($config, $container), $config);
33 | $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config/container'));
34 |
35 | $container->setAlias('sylius.process_storage', $config['storage']);
36 |
37 | $configurations = [
38 | 'builders',
39 | 'validators',
40 | 'contexts',
41 | 'controllers',
42 | 'coordinators',
43 | 'storages',
44 | ];
45 |
46 | foreach ($configurations as $basename) {
47 | $loader->load(sprintf('%s.xml', $basename));
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/spec/DependencyInjection/Compiler/RegisterStepsPassSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\DependencyInjection\Compiler\RegisterStepsPass');
25 | }
26 |
27 | function it_is_compiler_pass()
28 | {
29 | $this->shouldImplement(CompilerPassInterface::class);
30 | }
31 |
32 | function it_processes(ContainerBuilder $container, Definition $coordinator)
33 | {
34 | $container->getDefinition('sylius.process.builder')->shouldBeCalled()->willreturn($coordinator);
35 | $container->findTaggedServiceIds('sylius.process.step')->shouldBeCalled()->willreturn([
36 | 'id' => [
37 | [
38 | 'alias' => 'alias',
39 | ],
40 | ],
41 | ]);
42 |
43 | $coordinator->addMethodCall('registerStep', Argument::type('array'))->shouldBeCalled();
44 |
45 | $this->process($container);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Process/Step/AbstractStep.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | abstract class AbstractStep implements StepInterface
22 | {
23 | /**
24 | * Step name in current scenario.
25 | *
26 | * @var string
27 | */
28 | protected $name;
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function getName()
34 | {
35 | return $this->name;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function setName($name)
42 | {
43 | $this->name = $name;
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function forwardAction(ProcessContextInterface $context)
50 | {
51 | return $this->complete();
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function isActive()
58 | {
59 | return true;
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function complete()
66 | {
67 | return new ActionResult();
68 | }
69 |
70 | /**
71 | * {@inheritdoc}
72 | */
73 | public function proceed($nextStepName)
74 | {
75 | return new ActionResult($nextStepName);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/spec/DependencyInjection/Compiler/RegisterScenariosPassSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\DependencyInjection\Compiler\RegisterScenariosPass');
25 | }
26 |
27 | function it_is_compiler_pass()
28 | {
29 | $this->shouldImplement(CompilerPassInterface::class);
30 | }
31 |
32 | function it_processes(ContainerBuilder $container, Definition $coordinator)
33 | {
34 | $container->getDefinition('sylius.process.coordinator')->shouldBeCalled()->willreturn($coordinator);
35 | $container->findTaggedServiceIds('sylius.process.scenario')->shouldBeCalled()->willreturn([
36 | 'id' => [
37 | [
38 | 'alias' => 'alias',
39 | ],
40 | ],
41 | ]);
42 |
43 | $coordinator->addMethodCall('registerScenario', Argument::type('array'))->shouldBeCalled();
44 |
45 | $this->process($container);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Storage/StorageInterface.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | interface StorageInterface
20 | {
21 | /**
22 | * Initializes storage for given domain.
23 | *
24 | * @param string $domain
25 | *
26 | * @return $this
27 | */
28 | public function initialize($domain);
29 |
30 | /**
31 | * Checks if the storage has a value for a key.
32 | *
33 | * @param string $key A unique key
34 | *
35 | * @return bool Whether the storage has a value for this key
36 | */
37 | public function has($key);
38 |
39 | /**
40 | * Returns the value for a key.
41 | *
42 | * @param string $key A unique key
43 | * @param mixed $default
44 | *
45 | * @return mixed|null The value in the storage or default if set or null if not found
46 | */
47 | public function get($key, $default = null);
48 |
49 | /**
50 | * Sets a value in the storage.
51 | *
52 | * @param string $key A unique key
53 | * @param string $value The value to storage
54 | */
55 | public function set($key, $value);
56 |
57 | /**
58 | * Removes a value from the storage.
59 | *
60 | * @param string $key A unique key
61 | */
62 | public function remove($key);
63 |
64 | /**
65 | * Clears all values from current domain.
66 | */
67 | public function clear();
68 | }
69 |
--------------------------------------------------------------------------------
/Process/Step/AbstractControllerStep.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | abstract class AbstractControllerStep extends Controller implements StepInterface
23 | {
24 | /**
25 | * Step name in current scenario.
26 | *
27 | * @var string
28 | */
29 | protected $name;
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function getName()
35 | {
36 | return $this->name;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function setName($name)
43 | {
44 | $this->name = $name;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function forwardAction(ProcessContextInterface $context)
51 | {
52 | return $this->complete();
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function isActive()
59 | {
60 | return true;
61 | }
62 |
63 | /**
64 | * {@inheritdoc}
65 | */
66 | public function complete()
67 | {
68 | return new ActionResult();
69 | }
70 |
71 | /**
72 | * {@inheritdoc}
73 | */
74 | public function proceed($nextStepName)
75 | {
76 | return new ActionResult($nextStepName);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Process/Step/StepInterface.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($container);
26 | }
27 |
28 | function it_is_initializable()
29 | {
30 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Process\Builder\ProcessBuilder');
31 | }
32 |
33 | function it_is_process_builder()
34 | {
35 | $this->shouldImplement(ProcessBuilderInterface::class);
36 | }
37 |
38 | function it_should_not_add_inactive_steps(
39 | StepInterface $step,
40 | ProcessScenarioInterface $scenario
41 | ) {
42 | $this->build($scenario);
43 | $step->isActive()->willReturn(false);
44 | $step->setName(Argument::any())->shouldNotBeCalled();
45 |
46 | $this->add('foobar', $step);
47 | }
48 |
49 | function it_should_add_active_steps(
50 | StepInterface $step,
51 | ProcessScenarioInterface $scenario
52 | ) {
53 | $step->getName()->willReturn(null);
54 | $this->build($scenario);
55 | $step->isActive()->willReturn(true);
56 | $step->setName('foobar')->shouldBeCalled();
57 |
58 | $this->add('foobar', $step);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Validator/ProcessValidatorInterface.php:
--------------------------------------------------------------------------------
1 |
24 | * @author Saša Stamenković
25 | */
26 | interface ProcessValidatorInterface
27 | {
28 | /**
29 | * Message to display on invalid.
30 | *
31 | * @param string $message
32 | *
33 | * @return ProcessValidatorInterface
34 | */
35 | public function setMessage($message);
36 |
37 | /**
38 | * Return message.
39 | *
40 | * @return string
41 | */
42 | public function getMessage();
43 |
44 | /**
45 | * Set step name to go on error.
46 | *
47 | * @param string $stepName
48 | *
49 | * @return ProcessValidatorInterface
50 | */
51 | public function setStepName($stepName);
52 |
53 | /**
54 | * Return step name to go on error.
55 | *
56 | * @return string
57 | */
58 | public function getStepName();
59 |
60 | /**
61 | * Check validation.
62 | *
63 | * @param ProcessContextInterface $processContext
64 | *
65 | * @return bool
66 | */
67 | public function isValid(ProcessContextInterface $processContext);
68 |
69 | /**
70 | * @param StepInterface $step
71 | *
72 | * @return ActionResult|Response|View
73 | */
74 | public function getResponse(StepInterface $step);
75 | }
76 |
--------------------------------------------------------------------------------
/spec/Storage/SessionStorageSpec.php:
--------------------------------------------------------------------------------
1 | getBag(SessionFlowsBag::NAME)->willReturn($bag);
23 |
24 | $this->beConstructedWith($session);
25 |
26 | $this->initialize('domain');
27 | }
28 |
29 | function it_is_initializable()
30 | {
31 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Storage\SessionStorage');
32 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Storage\AbstractStorage');
33 | $this->shouldImplement('Sylius\Bundle\FlowBundle\Storage\StorageInterface');
34 | }
35 |
36 | function itd_message_is_mutable($bag)
37 | {
38 | $bag->set('domain/message_key', 'message_value')->shouldBeCalled();
39 |
40 | $this->set('message_key', 'message_value');
41 | }
42 |
43 | function it_has_message($bag)
44 | {
45 | $bag->get('domain/message_key', null)->shouldBeCalled();
46 |
47 | $this->get('message_key');
48 | }
49 |
50 | function it_checks_is_the_message_exists($bag)
51 | {
52 | $bag->has('domain/message_key')->shouldBeCalled();
53 |
54 | $this->has('message_key');
55 | }
56 |
57 | function it_removes_a_message($bag)
58 | {
59 | $bag->remove('domain/message_key')->shouldBeCalled();
60 |
61 | $this->remove('message_key');
62 | }
63 |
64 | function it_removes_all_messages($bag)
65 | {
66 | $bag->remove('domain/message_key')->shouldBeCalled();
67 |
68 | $this->remove('message_key');
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/spec/Validator/ProcessValidatorSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith('message', 'step_name', function () {});
24 | }
25 |
26 | function it_is_initializable()
27 | {
28 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Validator\ProcessValidator');
29 | }
30 |
31 | function it_is_process_validator()
32 | {
33 | $this->shouldImplement(ProcessValidatorInterface::class);
34 | }
35 |
36 | function its_step_name_is_mutable()
37 | {
38 | $this->setStepName('step_name')->shouldReturn($this);
39 | $this->getStepName()->shouldReturn('step_name');
40 | }
41 |
42 | function its_message_is_mutable()
43 | {
44 | $this->setMessage('message')->shouldReturn($this);
45 | $this->getMessage()->shouldReturn('message');
46 | }
47 |
48 | function its_validation_is_mutable()
49 | {
50 | $closure = function () {};
51 |
52 | $this->setValidation($closure)->shouldReturn($this);
53 | $this->getValidation()->shouldReturn($closure);
54 | }
55 |
56 | function it_calls_validation_closure(ProcessContextInterface $processContext)
57 | {
58 | $this->setValidation(function () {
59 | return true;
60 | });
61 |
62 | $this->isValid($processContext)->shouldReturn(true);
63 | }
64 |
65 | function it_has_response(StepInterface $step)
66 | {
67 | $this->setStepName('step_name');
68 | $step->proceed('step_name')->shouldBeCalled();
69 |
70 | $this->getResponse($step);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Process/Coordinator/CoordinatorInterface.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | interface CoordinatorInterface
27 | {
28 | /**
29 | * Start scenario, should redirect to first step.
30 | *
31 | * @param string $scenarioAlias
32 | * @param ParameterBag $queryParameters
33 | *
34 | * @return RedirectResponse
35 | */
36 | public function start($scenarioAlias, ParameterBag $queryParameters = null);
37 |
38 | /**
39 | * Display step.
40 | *
41 | * @param string $scenarioAlias
42 | * @param string $stepName
43 | * @param ParameterBag $queryParameters
44 | *
45 | * @return Response|View
46 | */
47 | public function display($scenarioAlias, $stepName, ParameterBag $queryParameters = null);
48 |
49 | /**
50 | * Move forward.
51 | * If step was completed, redirect to next step, otherwise return response.
52 | *
53 | * @param string $scenarioAlias
54 | * @param string $stepName
55 | *
56 | * @return Response|View
57 | */
58 | public function forward($scenarioAlias, $stepName);
59 |
60 | /**
61 | * Register new process scenario.
62 | *
63 | * @param string $alias
64 | * @param ProcessScenarioInterface $scenario
65 | */
66 | public function registerScenario($alias, ProcessScenarioInterface $scenario);
67 |
68 | /**
69 | * Load process scenario with given alias.
70 | *
71 | * @param string $scenario
72 | *
73 | * @return ProcessScenarioInterface
74 | */
75 | public function loadScenario($scenario);
76 | }
77 |
--------------------------------------------------------------------------------
/Storage/SessionStorage.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class SessionStorage extends AbstractStorage
22 | {
23 | /**
24 | * Session.
25 | *
26 | * @var SessionInterface
27 | */
28 | protected $session;
29 |
30 | /**
31 | * Constructor.
32 | *
33 | * @param SessionInterface $session
34 | */
35 | public function __construct(SessionInterface $session)
36 | {
37 | $this->session = $session;
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | public function get($key, $default = null)
44 | {
45 | return $this->getBag()->get($this->resolveKey($key), $default);
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function set($key, $value)
52 | {
53 | $this->getBag()->set($this->resolveKey($key), $value);
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function has($key)
60 | {
61 | return $this->getBag()->has($this->resolveKey($key));
62 | }
63 |
64 | /**
65 | * {@inheritdoc}
66 | */
67 | public function remove($key)
68 | {
69 | $this->getBag()->remove($this->resolveKey($key));
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function clear()
76 | {
77 | $this->getBag()->remove($this->domain);
78 | }
79 |
80 | /**
81 | * Get session flows bag.
82 | *
83 | * @return SessionFlowsBag
84 | */
85 | private function getBag()
86 | {
87 | return $this->session->getBag(SessionFlowsBag::NAME);
88 | }
89 |
90 | /**
91 | * Resolve key for current domain.
92 | *
93 | * @param string $key
94 | *
95 | * @return string
96 | */
97 | private function resolveKey($key)
98 | {
99 | return $this->domain.'/'.$key;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Controller/ProcessController.php:
--------------------------------------------------------------------------------
1 |
25 | */
26 | class ProcessController
27 | {
28 | /**
29 | * @var CoordinatorInterface
30 | */
31 | protected $processCoordinator;
32 |
33 | /**
34 | * @var ProcessContextInterface
35 | */
36 | protected $processContext;
37 |
38 | public function __construct(CoordinatorInterface $processCoordinator, ProcessContextInterface $processContext)
39 | {
40 | $this->processCoordinator = $processCoordinator;
41 | $this->processContext = $processContext;
42 | }
43 |
44 | /**
45 | * Build and start process for given scenario.
46 | * This action usually redirects to first step.
47 | *
48 | * @param Request $request
49 | * @param string $scenarioAlias
50 | *
51 | * @return Response
52 | */
53 | public function startAction(Request $request, $scenarioAlias)
54 | {
55 | return $this->processCoordinator->start($scenarioAlias, $request->query);
56 | }
57 |
58 | /**
59 | * Execute display action of given step.
60 | *
61 | * @param Request $request
62 | * @param string $scenarioAlias
63 | * @param string $stepName
64 | *
65 | * @throws NotFoundHttpException
66 | *
67 | * @return Response
68 | */
69 | public function displayAction(Request $request, $scenarioAlias, $stepName)
70 | {
71 | $this->processContext->setRequest($request);
72 |
73 | try {
74 | return $this->processCoordinator->display($scenarioAlias, $stepName, $request->query);
75 | } catch (InvalidArgumentException $e) {
76 | throw new NotFoundHttpException('The step you are looking for is not found.', $e);
77 | }
78 | }
79 |
80 | /**
81 | * Execute continue action of given step.
82 | *
83 | * @param Request $request
84 | * @param string $scenarioAlias
85 | * @param string $stepName
86 | *
87 | * @return Response
88 | */
89 | public function forwardAction(Request $request, $scenarioAlias, $stepName)
90 | {
91 | $this->processContext->setRequest($request);
92 |
93 | return $this->processCoordinator->forward($scenarioAlias, $stepName);
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/spec/Process/ProcessSpec.php:
--------------------------------------------------------------------------------
1 | shouldHaveType('Sylius\Bundle\FlowBundle\Process\Process');
24 | }
25 |
26 | function it_is_a_process()
27 | {
28 | $this->shouldImplement(ProcessInterface::class);
29 | }
30 |
31 | function its_forward_route_is_mutable()
32 | {
33 | $this->setForwardRoute('forward_route');
34 | $this->getForwardRoute()->shouldReturn('forward_route');
35 | }
36 |
37 | function its_forward_route_params_is_mutable()
38 | {
39 | $this->setForwardRouteParams(['name' => 'value']);
40 | $this->getForwardRouteParams()->shouldReturn(['name' => 'value']);
41 | }
42 |
43 | function its_display_route_is_mutable()
44 | {
45 | $this->setDisplayRoute('display_route');
46 | $this->getDisplayRoute()->shouldReturn('display_route');
47 | }
48 |
49 | function its_display_params_is_mutable()
50 | {
51 | $this->setDisplayRouteParams(['name' => 'value']);
52 | $this->getDisplayRouteParams()->shouldReturn(['name' => 'value']);
53 | }
54 |
55 | function its_redirect_params_is_mutable()
56 | {
57 | $this->setRedirectParams(['name' => 'value']);
58 | $this->getRedirectParams()->shouldReturn(['name' => 'value']);
59 | }
60 |
61 | function its_redirect_is_mutable()
62 | {
63 | $this->setRedirect('redirect');
64 | $this->getRedirect()->shouldReturn('redirect');
65 | }
66 |
67 | function its_scenario_is_mutable()
68 | {
69 | $this->setScenarioAlias('scenarioAlias');
70 | $this->getScenarioAlias()->shouldReturn('scenarioAlias');
71 | }
72 |
73 | function its_validator_is_mutable(ProcessValidatorInterface $validator)
74 | {
75 | $this->setValidator($validator);
76 | $this->getValidator()->shouldReturn($validator);
77 | }
78 |
79 | function its_step_is_mutable(StepInterface $step, StepInterface $secondStep)
80 | {
81 | $step->getName()->shouldBeCalled()->willReturn('name');
82 | $secondStep->getName()->shouldBeCalled()->willReturn('other_name');
83 | $this->setSteps(['name' => $step]);
84 |
85 | $this->addStep('other_name', $secondStep);
86 | $this->removeStep('name');
87 |
88 | $this->getSteps()->shouldReturn(['other_name' => $secondStep]);
89 | $this->getOrderedSteps()->shouldReturn([$secondStep]);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Validator/ProcessValidator.php:
--------------------------------------------------------------------------------
1 |
21 | * @author Saša Stamenković
22 | */
23 | class ProcessValidator implements ProcessValidatorInterface
24 | {
25 | /**
26 | * @var string|null
27 | */
28 | protected $message;
29 |
30 | /**
31 | * @var string|null
32 | */
33 | protected $stepName;
34 |
35 | /**
36 | * @var callable
37 | */
38 | protected $validation;
39 |
40 | public function __construct($message = null, $stepName = null, \Closure $validation = null)
41 | {
42 | $this->message = $message;
43 | $this->stepName = $stepName;
44 | $this->validation = $validation;
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function setStepName($stepName)
51 | {
52 | $this->stepName = $stepName;
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function getStepName()
61 | {
62 | return $this->stepName;
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function setMessage($message)
69 | {
70 | $this->message = $message;
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function getMessage()
79 | {
80 | return $this->message;
81 | }
82 |
83 | /**
84 | * Set validation.
85 | *
86 | * @param callable $validation
87 | *
88 | * @return $this
89 | */
90 | public function setValidation(\Closure $validation)
91 | {
92 | $this->validation = $validation;
93 |
94 | return $this;
95 | }
96 |
97 | /**
98 | * Get validation.
99 | *
100 | * @return callable
101 | */
102 | public function getValidation()
103 | {
104 | return $this->validation;
105 | }
106 |
107 | /**
108 | * {@inheritdoc}
109 | */
110 | public function isValid(ProcessContextInterface $processContext)
111 | {
112 | return call_user_func($this->validation) ? true : false;
113 | }
114 |
115 | /**
116 | * {@inheritdoc}
117 | */
118 | public function getResponse(StepInterface $step)
119 | {
120 | if ($this->getStepName()) {
121 | return $step->proceed($this->getStepName());
122 | }
123 |
124 | throw new ProcessValidatorException(400, $this->getMessage());
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Process/Builder/ProcessBuilderInterface.php:
--------------------------------------------------------------------------------
1 |
23 | */
24 | interface ProcessBuilderInterface
25 | {
26 | /**
27 | * Build process by adding steps defined in scenario.
28 | *
29 | * @param ProcessScenarioInterface $scenario
30 | *
31 | * @return ProcessInterface
32 | */
33 | public function build(ProcessScenarioInterface $scenario);
34 |
35 | /**
36 | * Add a step with given name.
37 | *
38 | * @param string $name
39 | * @param string|StepInterface $step Step alias or instance
40 | *
41 | * @return ProcessBuilderInterface
42 | */
43 | public function add($name, $step);
44 |
45 | /**
46 | * Remove step with given name.
47 | *
48 | * @param string $name
49 | */
50 | public function remove($name);
51 |
52 | /**
53 | * Check whether or not process has given step.
54 | *
55 | * @param string $name
56 | *
57 | * @return bool
58 | */
59 | public function has($name);
60 |
61 | /**
62 | * Set display route.
63 | *
64 | * @param string $route
65 | */
66 | public function setDisplayRoute($route);
67 |
68 | /**
69 | * Set additional forward route params.
70 | *
71 | * @param array $params
72 | */
73 | public function setDisplayRouteParams(array $params);
74 |
75 | /**
76 | * Set forward route.
77 | *
78 | * @param string $route
79 | */
80 | public function setForwardRoute($route);
81 |
82 | /**
83 | * Set additional forward route params.
84 | *
85 | * @param array $params
86 | */
87 | public function setForwardRouteParams(array $params);
88 |
89 | /**
90 | * Set redirection route after completion.
91 | *
92 | * @param string $redirect
93 | */
94 | public function setRedirect($redirect);
95 |
96 | /**
97 | * Set redirection route params.
98 | *
99 | * @param array $params
100 | */
101 | public function setRedirectParams(array $params);
102 |
103 | /**
104 | * Validation of process, if returns false, process is suspended.
105 | *
106 | * @param \Closure|ProcessValidatorInterface $validator
107 | */
108 | public function validate($validator);
109 |
110 | /**
111 | * Register new step.
112 | *
113 | * @param string $alias
114 | * @param StepInterface $step
115 | */
116 | public function registerStep($alias, StepInterface $step);
117 |
118 | /**
119 | * Load step.
120 | *
121 | * @param string $alias
122 | *
123 | * @return StepInterface
124 | */
125 | public function loadStep($alias);
126 | }
127 |
--------------------------------------------------------------------------------
/Process/Context/ProcessContextInterface.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | interface ProcessContextInterface
26 | {
27 | /**
28 | * Initialize context with process and current step.
29 | *
30 | * @param ProcessInterface $process
31 | * @param StepInterface $currentStep
32 | */
33 | public function initialize(ProcessInterface $process, StepInterface $currentStep);
34 |
35 | /**
36 | * Get process.
37 | *
38 | * @return ProcessInterface
39 | */
40 | public function getProcess();
41 |
42 | /**
43 | * Get current step.
44 | *
45 | * @return StepInterface
46 | */
47 | public function getCurrentStep();
48 |
49 | /**
50 | * Get previous step.
51 | *
52 | * @return StepInterface
53 | */
54 | public function getPreviousStep();
55 |
56 | /**
57 | * Get next step.
58 | *
59 | * @return StepInterface
60 | */
61 | public function getNextStep();
62 |
63 | /**
64 | * Is current step the first step?
65 | *
66 | * @return bool
67 | */
68 | public function isFirstStep();
69 |
70 | /**
71 | * Is current step the last step?
72 | *
73 | * @return bool
74 | */
75 | public function isLastStep();
76 |
77 | /**
78 | * Override the default next step.
79 | *
80 | * @param string $stepAlias
81 | */
82 | public function setNextStepByName($stepAlias);
83 |
84 | /**
85 | * Close context and clear all the data.
86 | */
87 | public function close();
88 |
89 | /**
90 | * Is current flow valid?
91 | *
92 | * @return bool
93 | */
94 | public function isValid();
95 |
96 | /**
97 | * Get storage.
98 | *
99 | * @return StorageInterface
100 | */
101 | public function getStorage();
102 |
103 | /**
104 | * Set storage.
105 | *
106 | * @param StorageInterface $storage
107 | */
108 | public function setStorage(StorageInterface $storage);
109 |
110 | /**
111 | * Get current request.
112 | *
113 | * @return Request
114 | */
115 | public function getRequest();
116 |
117 | /**
118 | * Set current request.
119 | *
120 | * @param Request $request
121 | */
122 | public function setRequest(Request $request);
123 |
124 | /**
125 | * Get progress in percents.
126 | *
127 | * @return int
128 | */
129 | public function getProgress();
130 |
131 | /**
132 | * The array contains the history of all the step names.
133 | *
134 | * @return array
135 | */
136 | public function getStepHistory();
137 |
138 | /**
139 | * Set a new history of step names.
140 | *
141 | * @param array $history
142 | */
143 | public function setStepHistory(array $history);
144 |
145 | /**
146 | * Add the given name to the history of step names.
147 | *
148 | * @param string $stepName
149 | */
150 | public function addStepToHistory($stepName);
151 |
152 | /**
153 | * Goes back from the end fo the history and deletes all step names until the current one is found.
154 | *
155 | * @throws NotFoundHttpException If the step name is not found in the history.
156 | */
157 | public function rewindHistory();
158 | }
159 |
--------------------------------------------------------------------------------
/Process/ProcessInterface.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | interface ProcessInterface
23 | {
24 | /**
25 | * Get scenario alias.
26 | *
27 | * @return string
28 | */
29 | public function getScenarioAlias();
30 |
31 | /**
32 | * Set scenario alias.
33 | *
34 | * @param string $scenarioAlias
35 | */
36 | public function setScenarioAlias($scenarioAlias);
37 |
38 | /**
39 | * Get a collection of steps.
40 | * Keys will be step names.
41 | *
42 | * @return StepInterface[]
43 | */
44 | public function getSteps();
45 |
46 | /**
47 | * Set steps.
48 | *
49 | * @param StepInterface[] $steps
50 | */
51 | public function setSteps(array $steps);
52 |
53 | /**
54 | * Get steps in correct order.
55 | *
56 | * @return StepInterface[]
57 | */
58 | public function getOrderedSteps();
59 |
60 | /**
61 | * Get first process step.
62 | *
63 | * @return StepInterface
64 | */
65 | public function getFirstStep();
66 |
67 | /**
68 | * Get last step.
69 | *
70 | * @return StepInterface
71 | */
72 | public function getLastStep();
73 |
74 | /**
75 | * Add step and name it.
76 | *
77 | * @param string $name
78 | * @param StepInterface $step
79 | */
80 | public function addStep($name, StepInterface $step);
81 |
82 | /**
83 | * Remove step.
84 | *
85 | * @param string $name
86 | */
87 | public function removeStep($name);
88 |
89 | /**
90 | * Has step with given name?
91 | *
92 | * @param string $name
93 | *
94 | * @return bool
95 | */
96 | public function hasStep($name);
97 |
98 | /**
99 | * Count all steps.
100 | *
101 | * @return int
102 | */
103 | public function countSteps();
104 |
105 | /**
106 | * Get validator.
107 | *
108 | * @return ProcessValidatorInterface
109 | */
110 | public function getValidator();
111 |
112 | /**
113 | * Set validator.
114 | *
115 | * @param ProcessValidatorInterface $validator
116 | *
117 | * @return $this
118 | */
119 | public function setValidator(ProcessValidatorInterface $validator);
120 |
121 | /**
122 | * Get redirection after complete.
123 | *
124 | * @return string
125 | */
126 | public function getRedirect();
127 |
128 | /**
129 | * Set redirection after complete.
130 | *
131 | * @param string $redirect
132 | */
133 | public function setRedirect($redirect);
134 |
135 | /**
136 | * Get redirection route params after complete.
137 | *
138 | * @return array
139 | */
140 | public function getRedirectParams();
141 |
142 | /**
143 | * Set redirection route params after complete.
144 | *
145 | * @param array $params
146 | */
147 | public function setRedirectParams(array $params);
148 |
149 | /**
150 | * Get display route.
151 | *
152 | * @return string
153 | */
154 | public function getDisplayRoute();
155 |
156 | /**
157 | * Set display route.
158 | *
159 | * @param string $route
160 | */
161 | public function setDisplayRoute($route);
162 |
163 | /**
164 | * Get additional display route parameters.
165 | *
166 | * @return array
167 | */
168 | public function getDisplayRouteParams();
169 |
170 | /**
171 | * Set additional display route params.
172 | *
173 | * @param array $params
174 | */
175 | public function setDisplayRouteParams(array $params);
176 |
177 | /**
178 | * Get forward route.
179 | *
180 | * @return string
181 | */
182 | public function getForwardRoute();
183 |
184 | /**
185 | * Set forward route.
186 | *
187 | * @param string $route
188 | */
189 | public function setForwardRoute($route);
190 |
191 | /**
192 | * Get additional forward route parameters.
193 | *
194 | * @return array
195 | */
196 | public function getForwardRouteParams();
197 |
198 | /**
199 | * Set additional forward route params.
200 | *
201 | * @param array $params
202 | */
203 | public function setForwardRouteParams(array $params);
204 |
205 | /**
206 | * Get step by index/order.
207 | *
208 | * @param int $index
209 | *
210 | * @return StepInterface
211 | */
212 | public function getStepByIndex($index);
213 |
214 | /**
215 | * Get step by name.
216 | *
217 | * @param string $index
218 | *
219 | * @return StepInterface
220 | */
221 | public function getStepByName($index);
222 | }
223 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SyliusFlowBundle [](http://travis-ci.org/Sylius/SyliusFlowBundle)
2 | ================
3 |
4 | Multiple action processes with reusable steps for [**Symfony2**](http://symfony.com) applications.
5 | Suitable for building checkouts or installations.
6 |
7 | > Note: This bundle is a **prototype**, it works only with latest Symfony.
8 |
9 | Sylius
10 | ------
11 |
12 | **Sylius** - Modern ecommerce for Symfony2. Visit [Sylius.org](http://sylius.org).
13 |
14 | Usage examples
15 | --------------
16 |
17 | ```php
18 |
30 | */
31 | class CheckoutScenario implements ProcessScenarioInterface
32 | {
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function build(ProcessBuilderInterface $builder)
37 | {
38 | $builder
39 | ->add('security', new Step\SecurityStep())
40 | ->add('delivery', new Step\DeliveryStep())
41 | ->add('billing', 'acme_checkout_step_billing')
42 | ->add('finalize', new Step\FinalizeStep())
43 | ;
44 | }
45 | }
46 | ```
47 |
48 | ```php
49 |
62 | */
63 | class DeliveryStep extends AbstractContainerAwareStep
64 | {
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function displayAction(ProcessContextInterface $context)
69 | {
70 | // All data is stored in a separate session bag with parameter namespace support.
71 | $address = $context->getStorage()->get('delivery.address');
72 | $form = $this->createAddressForm($address);
73 |
74 | return $this->container->get('templating')->renderResponse('AcmeCheckoutBundle:Step:delivery.html.twig', array(
75 | 'form' => $form->createView(),
76 | 'context' => $context
77 | ));
78 | }
79 |
80 | /**
81 | * {@inheritdoc}
82 | */
83 | public function forwardAction(ProcessContextInterface $context)
84 | {
85 | $request = $context->getRequest();
86 | $form = $this->createAddressForm();
87 |
88 | if ($form->handleRequest($request)->isValid()) {
89 | $context->getStorage()->set('delivery.address', $form->getData());
90 |
91 | return $this->complete();
92 | }
93 |
94 | // Form was not valid so we display the form again.
95 | return $this->container->get('templating')->renderResponse('AcmeCheckoutBundle:Step:delivery.html.twig', array(
96 | 'form' => $form->createView(),
97 | 'context' => $context
98 | ));
99 | }
100 |
101 | /**
102 | * Create address form.
103 | *
104 | * @param Address $address
105 | *
106 | * @return FormInterface
107 | */
108 | private function createAddressForm(Address $address = null)
109 | {
110 | return $this->container->get('form.factory')->create('acme_checkout_address', $address);
111 | }
112 | }
113 | ```
114 |
115 | [phpunit](http://phpunit.de) tests
116 | ----------------------------------
117 |
118 | ``` bash
119 | $ composer install
120 | $ phpunit
121 | ```
122 |
123 | Documentation
124 | -------------
125 |
126 | Documentation is available on [**docs.sylius.org**](http://docs.sylius.org/en/latest/bundles/SyliusFlowBundle/index.html).
127 |
128 | Code examples
129 | -------------
130 |
131 | If you want to see working implementation, try out the [Sylius sandbox application](http://github.com/Sylius/Sylius-Sandbox).
132 |
133 | Contributing
134 | ------------
135 |
136 | All informations about contributing to Sylius can be found on [this page](http://docs.sylius.org/en/latest/contributing/index.html).
137 |
138 | Mailing lists
139 | -------------
140 |
141 | ### Users
142 |
143 | Questions? Feel free to ask on [users mailing list](http://groups.google.com/group/sylius).
144 |
145 | ### Developers
146 |
147 | To contribute and develop this bundle, use the [developers mailing list](http://groups.google.com/group/sylius-dev).
148 |
149 | Sylius twitter account
150 | ----------------------
151 |
152 | If you want to keep up with updates, [follow the official Sylius account on twitter](http://twitter.com/Sylius).
153 |
154 | Bug tracking
155 | ------------
156 |
157 | This bundle uses [GitHub issues](https://github.com/Sylius/Sylius/issues).
158 | If you have found bug, please create an issue.
159 |
160 | Versioning
161 | ----------
162 |
163 | Releases will be numbered with the format `major.minor.patch`.
164 |
165 | And constructed with the following guidelines.
166 |
167 | * Breaking backwards compatibility bumps the major.
168 | * New additions without breaking backwards compatibility bumps the minor.
169 | * Bug fixes and misc changes bump the patch.
170 |
171 | For more information on SemVer, please visit [semver.org website](http://semver.org/).
172 | This versioning method is same for all **Sylius** bundles and applications.
173 |
174 | MIT License
175 | -----------
176 |
177 | License can be found [here](https://github.com/Sylius/SyliusFlowBundle/blob/master/Resources/meta/LICENSE).
178 |
179 | Authors
180 | -------
181 |
182 | The bundle was originally created by [Paweł Jędrzejewski](http://pjedrzejewski.com).
183 | See the list of [contributors](https://github.com/Sylius/SyliusFlowBundle/contributors).
184 |
--------------------------------------------------------------------------------
/Process/Builder/ProcessBuilder.php:
--------------------------------------------------------------------------------
1 | container = $container;
53 | }
54 |
55 | /**
56 | * {@inheritdoc}
57 | */
58 | public function build(ProcessScenarioInterface $scenario)
59 | {
60 | $this->process = new Process();
61 |
62 | $scenario->build($this);
63 |
64 | return $this->process;
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function add($name, $step)
71 | {
72 | $this->assertHasProcess();
73 |
74 | if (is_string($step)) {
75 | $step = $this->loadStep($step);
76 | }
77 |
78 | if (!$step instanceof StepInterface) {
79 | throw new \InvalidArgumentException('Step added via builder must implement "Sylius\Bundle\FlowBundle\Process\Step\StepInterface"');
80 | }
81 |
82 | if (!$step->isActive()) {
83 | return $this;
84 | }
85 |
86 | if ($step instanceof ContainerAwareInterface) {
87 | $step->setContainer($this->container);
88 | }
89 |
90 | $step->setName($name);
91 |
92 | $this->process->addStep($name, $step);
93 |
94 | return $this;
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public function remove($name)
101 | {
102 | $this->assertHasProcess();
103 |
104 | $this->process->removeStep($name);
105 | }
106 |
107 | /**
108 | * {@inheritdoc}
109 | */
110 | public function has($name)
111 | {
112 | $this->assertHasProcess();
113 |
114 | return $this->process->hasStep($name);
115 | }
116 |
117 | /**
118 | * {@inheritdoc}
119 | */
120 | public function setDisplayRoute($route)
121 | {
122 | $this->assertHasProcess();
123 |
124 | $this->process->setDisplayRoute($route);
125 |
126 | return $this;
127 | }
128 |
129 | /**
130 | * {@inheritdoc}
131 | */
132 | public function setDisplayRouteParams(array $params)
133 | {
134 | $this->assertHasProcess();
135 |
136 | $this->process->setDisplayRouteParams($params);
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * {@inheritdoc}
143 | */
144 | public function setForwardRoute($route)
145 | {
146 | $this->assertHasProcess();
147 |
148 | $this->process->setForwardRoute($route);
149 |
150 | return $this;
151 | }
152 |
153 | /**
154 | * {@inheritdoc}
155 | */
156 | public function setForwardRouteParams(array $params)
157 | {
158 | $this->assertHasProcess();
159 |
160 | $this->process->setForwardRouteParams($params);
161 |
162 | return $this;
163 | }
164 |
165 | /**
166 | * {@inheritdoc}
167 | */
168 | public function setRedirect($redirect)
169 | {
170 | $this->assertHasProcess();
171 |
172 | $this->process->setRedirect($redirect);
173 |
174 | return $this;
175 | }
176 |
177 | /**
178 | * {@inheritdoc}
179 | */
180 | public function setRedirectParams(array $params)
181 | {
182 | $this->assertHasProcess();
183 |
184 | $this->process->setRedirectParams($params);
185 |
186 | return $this;
187 | }
188 |
189 | /**
190 | * {@inheritdoc}
191 | */
192 | public function validate($validator)
193 | {
194 | $this->assertHasProcess();
195 |
196 | if ($validator instanceof \Closure) {
197 | $validator = $this->container->get('sylius.process.validator')->setValidation($validator);
198 | }
199 |
200 | if (!$validator instanceof ProcessValidatorInterface) {
201 | throw new \InvalidArgumentException();
202 | }
203 |
204 | $this->process->setValidator($validator);
205 |
206 | return $this;
207 | }
208 |
209 | /**
210 | * {@inheritdoc}
211 | */
212 | public function registerStep($alias, StepInterface $step)
213 | {
214 | if (isset($this->steps[$alias])) {
215 | throw new \InvalidArgumentException(sprintf('Flow step with alias "%s" is already registered', $alias));
216 | }
217 |
218 | $this->steps[$alias] = $step;
219 | }
220 |
221 | /**
222 | * {@inheritdoc}
223 | */
224 | public function loadStep($alias)
225 | {
226 | if (!isset($this->steps[$alias])) {
227 | throw new \InvalidArgumentException(sprintf('Flow step with alias "%s" is not registered', $alias));
228 | }
229 |
230 | return $this->steps[$alias];
231 | }
232 |
233 | /**
234 | * If process do not exists, throw exception.
235 | *
236 | * @throws \RuntimeException
237 | */
238 | protected function assertHasProcess()
239 | {
240 | if (!$this->process) {
241 | throw new \RuntimeException('Process is not set');
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/spec/Process/Context/ProcessContextSpec.php:
--------------------------------------------------------------------------------
1 | beConstructedWith($storage);
28 | }
29 |
30 | function it_is_initializable()
31 | {
32 | $this->shouldHaveType('Sylius\Bundle\FlowBundle\Process\Context\ProcessContext');
33 | }
34 |
35 | function it_is_a_process_context()
36 | {
37 | $this->shouldImplement(ProcessContextInterface::class);
38 | }
39 |
40 | function it_initializes(
41 | $storage,
42 | ProcessInterface $process,
43 | StepInterface $currentStep,
44 | StepInterface $previousStep,
45 | StepInterface $nextStep
46 | ) {
47 | $process->getScenarioAlias()->shouldBeCalled();
48 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
49 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$previousStep, $currentStep, $nextStep]);
50 | $process->countSteps()->shouldBeCalled()->willReturn(3);
51 |
52 | $this->initialize($process, $currentStep);
53 |
54 | $this->getNextStep()->shouldReturn($nextStep);
55 | $this->getCurrentStep()->shouldReturn($currentStep);
56 | $this->getPreviousStep()->shouldReturn($previousStep);
57 | }
58 |
59 | function it_is_valid(
60 | $storage,
61 | ProcessInterface $process,
62 | StepInterface $currentStep,
63 | StepInterface $previousStep,
64 | StepInterface $nextStep,
65 | ProcessValidatorInterface $processValidator
66 | ) {
67 | $process->getScenarioAlias()->shouldBeCalled();
68 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
69 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$previousStep, $currentStep, $nextStep]);
70 | $process->countSteps()->shouldBeCalled()->willReturn(3);
71 |
72 | $this->initialize($process, $currentStep);
73 |
74 | $process->getValidator()->willReturn($processValidator);
75 | $processValidator->isValid($this)->willReturn(false);
76 | $this->isValid()->shouldReturn(false);
77 |
78 | $process->getValidator()->willReturn($processValidator);
79 | $processValidator->isValid($this)->willReturn(true);
80 |
81 | $process->getValidator()->willReturn(null);
82 | $currentStep->getName()->willReturn('current_step');
83 | $storage->get('history', [])->shouldBeCalled()->willReturn([]);
84 | $this->isValid()->shouldReturn(true);
85 |
86 | $process->getValidator()->willReturn(null);
87 | $currentStep->getName()->shouldBeCalled()->willReturn('current_step');
88 | $storage->get('history', [])->shouldBeCalled()->willReturn(['current_step']);
89 | $this->isValid()->shouldReturn(true);
90 |
91 | $process->getValidator()->willReturn(null);
92 | $currentStep->getName()->shouldBeCalled()->willReturn('other_step');
93 | $storage->get('history', [])->shouldBeCalled()->willReturn(['current_step']);
94 | $this->isValid()->shouldReturn(false);
95 | }
96 |
97 | function it_checks_if_it_is_the_first_step(
98 | $storage,
99 | ProcessInterface $process,
100 | StepInterface $firstStep,
101 | StepInterface $lastStep
102 | ) {
103 | $process->getScenarioAlias()->shouldBeCalled();
104 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
105 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$firstStep, $lastStep]);
106 | $process->countSteps()->shouldBeCalled()->willReturn(2);
107 |
108 | $this->initialize($process, $firstStep);
109 |
110 | $this->isFirstStep()->shouldReturn(true);
111 | }
112 |
113 | function it_checks_if_it_is_the_last_step(
114 | $storage,
115 | ProcessInterface $process,
116 | StepInterface $firstStep,
117 | StepInterface $lastStep
118 | ) {
119 | $process->getScenarioAlias()->shouldBeCalled();
120 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
121 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$firstStep, $lastStep]);
122 | $process->countSteps()->shouldBeCalled()->willReturn(2);
123 |
124 | $this->initialize($process, $lastStep);
125 |
126 | $this->isLastStep()->shouldReturn(true);
127 | }
128 |
129 | function it_closes_the_storage(
130 | $storage,
131 | ProcessInterface $process,
132 | StepInterface $firstStep,
133 | StepInterface $lastStep
134 | ) {
135 | $process->getScenarioAlias()->shouldBeCalled();
136 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
137 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$firstStep, $lastStep]);
138 | $process->countSteps()->shouldBeCalled()->willReturn(2);
139 |
140 | $this->initialize($process, $lastStep);
141 |
142 | $storage->clear()->shouldBeCalled();
143 |
144 | $this->close();
145 | }
146 |
147 | function its_request_is_mutable(Request $request)
148 | {
149 | $this->setRequest($request);
150 | $this->getRequest()->shouldReturn($request);
151 | }
152 |
153 | function its_storage_is_mutable(StorageInterface $storage)
154 | {
155 | $this->setStorage($storage);
156 | $this->getStorage()->shouldReturn($storage);
157 | }
158 |
159 | function its_step_history_is_mutable($storage)
160 | {
161 | $storage->set('history', ['step_one'])->shouldBeCalled();
162 | $storage->get('history', [])->willReturn(['step_one']);
163 | $storage->set('history', ['step_one', 'step_two'])->shouldBeCalled();
164 | $storage->get('history', ['step_one'])->willReturn(['step_one', 'step_two']);
165 |
166 | $this->setStepHistory(['step_one']);
167 | $this->addStepToHistory('step_two');
168 | }
169 |
170 | function it_rewind_history(
171 | $storage,
172 | ProcessInterface $process,
173 | StepInterface $currentStep,
174 | StepInterface $previousStep,
175 | StepInterface $nextStep
176 | ) {
177 | $currentStep->getName()->willReturn('step_two');
178 | $process->getScenarioAlias()->shouldBeCalled();
179 | $storage->initialize(Argument::type('string'))->shouldBeCalled();
180 | $process->getOrderedSteps()->shouldBeCalled()->willReturn([$previousStep, $currentStep, $nextStep]);
181 | $process->countSteps()->shouldBeCalled()->willReturn(2);
182 | $this->initialize($process, $currentStep);
183 |
184 | $storage->get('history', [])->shouldBeCalled()->willreturn(['step_one', 'step_two', 'step_three']);
185 | $storage->set('history', ['step_one', 'step_two'])->shouldBeCalled();
186 |
187 | $this->rewindHistory();
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/Process/Process.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class Process implements ProcessInterface
24 | {
25 | /**
26 | * Process scenario alias.
27 | *
28 | * @var string
29 | */
30 | protected $scenarioAlias;
31 |
32 | /**
33 | * Steps.
34 | *
35 | * @var StepInterface[]
36 | */
37 | protected $steps = [];
38 |
39 | /**
40 | * Ordered steps.
41 | *
42 | * @var StepInterface[]
43 | */
44 | protected $orderedSteps = [];
45 |
46 | /**
47 | * @var ProcessValidatorInterface
48 | */
49 | protected $validator;
50 |
51 | /**
52 | * Display action route.
53 | *
54 | * @var string
55 | */
56 | protected $displayRoute;
57 |
58 | /**
59 | * Display action route params.
60 | *
61 | * @var array
62 | */
63 | protected $displayRouteParams = [];
64 |
65 | /**
66 | * Forward action route.
67 | *
68 | * @var string
69 | */
70 | protected $forwardRoute;
71 |
72 | /**
73 | * Forward action route params.
74 | *
75 | * @var array
76 | */
77 | protected $forwardRouteParams = [];
78 |
79 | /**
80 | * Redirect route.
81 | *
82 | * @var string
83 | */
84 | protected $redirect;
85 |
86 | /**
87 | * Redirect route params.
88 | *
89 | * @var array
90 | */
91 | protected $redirectParams = [];
92 |
93 | /**
94 | * {@inheritdoc}
95 | */
96 | public function getScenarioAlias()
97 | {
98 | return $this->scenarioAlias;
99 | }
100 |
101 | /**
102 | * {@inheritdoc}
103 | */
104 | public function setScenarioAlias($scenarioAlias)
105 | {
106 | $this->scenarioAlias = $scenarioAlias;
107 | }
108 |
109 | /**
110 | * {@inheritdoc}
111 | */
112 | public function getSteps()
113 | {
114 | return $this->steps;
115 | }
116 |
117 | /**
118 | * {@inheritdoc}
119 | */
120 | public function setSteps(array $steps)
121 | {
122 | foreach ($steps as $name => $step) {
123 | $this->addStep($name, $step);
124 | }
125 | }
126 |
127 | /**
128 | * {@inheritdoc}
129 | */
130 | public function getOrderedSteps()
131 | {
132 | return $this->orderedSteps;
133 | }
134 |
135 | /**
136 | * {@inheritdoc}
137 | */
138 | public function getStepByIndex($index)
139 | {
140 | if (!isset($this->orderedSteps[$index])) {
141 | throw new InvalidArgumentException(sprintf('Step with index %d. does not exist', $index));
142 | }
143 |
144 | return $this->orderedSteps[$index];
145 | }
146 |
147 | /**
148 | * {@inheritdoc}
149 | */
150 | public function getStepByName($name)
151 | {
152 | if (!$this->hasStep($name)) {
153 | throw new InvalidArgumentException(sprintf('Step with name "%s" does not exist', $name));
154 | }
155 |
156 | return $this->steps[$name];
157 | }
158 |
159 | /**
160 | * {@inheritdoc}
161 | */
162 | public function getFirstStep()
163 | {
164 | return $this->getStepByIndex(0);
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function getLastStep()
171 | {
172 | return $this->getStepByIndex($this->countSteps() - 1);
173 | }
174 |
175 | /**
176 | * {@inheritdoc}
177 | */
178 | public function countSteps()
179 | {
180 | return count($this->steps);
181 | }
182 |
183 | /**
184 | * {@inheritdoc}
185 | */
186 | public function addStep($name, StepInterface $step)
187 | {
188 | if ($this->hasStep($name)) {
189 | throw new InvalidArgumentException(sprintf('Step with name "%s" already exists', $name));
190 | }
191 |
192 | if (null === $step->getName()) {
193 | $step->setName($name);
194 | }
195 |
196 | $this->steps[$name] = $this->orderedSteps[] = $step;
197 | }
198 |
199 | /**
200 | * {@inheritdoc}
201 | */
202 | public function removeStep($name)
203 | {
204 | if (!$this->hasStep($name)) {
205 | throw new InvalidArgumentException(sprintf('Step with name "%s" does not exist', $name));
206 | }
207 |
208 | $index = array_search($this->steps[$name], $this->orderedSteps);
209 |
210 | unset($this->steps[$name], $this->orderedSteps[$index]);
211 | $this->orderedSteps = array_values($this->orderedSteps); //keep sequential index intact
212 | }
213 |
214 | /**
215 | * {@inheritdoc}
216 | */
217 | public function hasStep($name)
218 | {
219 | return isset($this->steps[$name]);
220 | }
221 |
222 | /**
223 | * {@inheritdoc}
224 | */
225 | public function getDisplayRoute()
226 | {
227 | return $this->displayRoute;
228 | }
229 |
230 | /**
231 | * {@inheritdoc}
232 | */
233 | public function setDisplayRoute($route)
234 | {
235 | $this->displayRoute = $route;
236 | }
237 |
238 | /**
239 | * {@inheritdoc}
240 | */
241 | public function getDisplayRouteParams()
242 | {
243 | return $this->displayRouteParams;
244 | }
245 |
246 | /**
247 | * {@inheritdoc}
248 | */
249 | public function setDisplayRouteParams(array $params)
250 | {
251 | $this->displayRouteParams = $params;
252 | }
253 |
254 | /**
255 | * {@inheritdoc}
256 | */
257 | public function getForwardRoute()
258 | {
259 | return $this->forwardRoute;
260 | }
261 |
262 | /**
263 | * {@inheritdoc}
264 | */
265 | public function setForwardRoute($route)
266 | {
267 | $this->forwardRoute = $route;
268 | }
269 |
270 | /**
271 | * {@inheritdoc}
272 | */
273 | public function getForwardRouteParams()
274 | {
275 | return $this->forwardRouteParams;
276 | }
277 |
278 | /**
279 | * {@inheritdoc}
280 | */
281 | public function setForwardRouteParams(array $params)
282 | {
283 | $this->forwardRouteParams = $params;
284 | }
285 |
286 | /**
287 | * {@inheritdoc}
288 | */
289 | public function getRedirect()
290 | {
291 | return $this->redirect;
292 | }
293 |
294 | /**
295 | * {@inheritdoc}
296 | */
297 | public function setRedirect($redirect)
298 | {
299 | $this->redirect = $redirect;
300 | }
301 |
302 | /**
303 | * {@inheritdoc}
304 | */
305 | public function getRedirectParams()
306 | {
307 | return $this->redirectParams;
308 | }
309 |
310 | /**
311 | * {@inheritdoc}
312 | */
313 | public function setRedirectParams(array $params)
314 | {
315 | $this->redirectParams = $params;
316 | }
317 |
318 | /**
319 | * {@inheritdoc}
320 | */
321 | public function getValidator()
322 | {
323 | return $this->validator;
324 | }
325 |
326 | /**
327 | * {@inheritdoc}
328 | */
329 | public function setValidator(ProcessValidatorInterface $validator)
330 | {
331 | $this->validator = $validator;
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/Process/Context/ProcessContext.php:
--------------------------------------------------------------------------------
1 |
24 | */
25 | class ProcessContext implements ProcessContextInterface
26 | {
27 | /**
28 | * Process.
29 | *
30 | * @var ProcessInterface
31 | */
32 | protected $process;
33 |
34 | /**
35 | * Current step.
36 | *
37 | * @var StepInterface
38 | */
39 | protected $currentStep;
40 |
41 | /**
42 | * Previous step.
43 | *
44 | * @var StepInterface
45 | */
46 | protected $previousStep;
47 |
48 | /**
49 | * Next step.
50 | *
51 | * @var StepInterface
52 | */
53 | protected $nextStep;
54 |
55 | /**
56 | * Storage.
57 | *
58 | * @var StorageInterface
59 | */
60 | protected $storage;
61 |
62 | /**
63 | * Request.
64 | *
65 | * @var Request
66 | */
67 | protected $request;
68 |
69 | /**
70 | * Progress in percents.
71 | *
72 | * @var int
73 | */
74 | protected $progress = 0;
75 |
76 | /**
77 | * Was the context initialized?
78 | *
79 | * @var bool
80 | */
81 | protected $initialized = false;
82 |
83 | /**
84 | * Constructor.
85 | *
86 | * @param StorageInterface $storage
87 | */
88 | public function __construct(StorageInterface $storage)
89 | {
90 | $this->storage = $storage;
91 | }
92 |
93 | /**
94 | * {@inheritdoc}
95 | */
96 | public function initialize(ProcessInterface $process, StepInterface $currentStep)
97 | {
98 | $this->process = $process;
99 | $this->currentStep = $currentStep;
100 |
101 | $this->storage->initialize(md5($process->getScenarioAlias()));
102 |
103 | $steps = $process->getOrderedSteps();
104 |
105 | foreach ($steps as $index => $step) {
106 | if ($step === $currentStep) {
107 | $this->previousStep = isset($steps[$index - 1]) ? $steps[$index - 1] : null;
108 | $this->nextStep = isset($steps[$index + 1]) ? $steps[$index + 1] : null;
109 |
110 | $this->calculateProgress($index);
111 | }
112 | }
113 |
114 | $this->initialized = true;
115 |
116 | return $this;
117 | }
118 |
119 | /**
120 | * {@inheritdoc}
121 | */
122 | public function isValid()
123 | {
124 | $this->assertInitialized();
125 |
126 | $validator = $this->process->getValidator();
127 |
128 | if (null !== $validator) {
129 | return $validator->isValid($this);
130 | }
131 |
132 | $history = $this->getStepHistory();
133 |
134 | return 0 === count($history) || in_array($this->currentStep->getName(), $history);
135 | }
136 |
137 | /**
138 | * {@inheritdoc}
139 | */
140 | public function getProcess()
141 | {
142 | $this->assertInitialized();
143 |
144 | return $this->process;
145 | }
146 |
147 | /**
148 | * {@inheritdoc}
149 | */
150 | public function getCurrentStep()
151 | {
152 | $this->assertInitialized();
153 |
154 | return $this->currentStep;
155 | }
156 |
157 | /**
158 | * {@inheritdoc}
159 | */
160 | public function getPreviousStep()
161 | {
162 | $this->assertInitialized();
163 |
164 | return $this->previousStep;
165 | }
166 |
167 | /**
168 | * {@inheritdoc}
169 | */
170 | public function getNextStep()
171 | {
172 | $this->assertInitialized();
173 |
174 | return $this->nextStep;
175 | }
176 |
177 | /**
178 | * {@inheritdoc}
179 | */
180 | public function isFirstStep()
181 | {
182 | $this->assertInitialized();
183 |
184 | return null === $this->previousStep;
185 | }
186 |
187 | /**
188 | * {@inheritdoc}
189 | */
190 | public function isLastStep()
191 | {
192 | $this->assertInitialized();
193 |
194 | return null === $this->nextStep;
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function close()
201 | {
202 | $this->assertInitialized();
203 |
204 | $this->storage->clear();
205 | }
206 |
207 | /**
208 | * {@inheritdoc}
209 | */
210 | public function getRequest()
211 | {
212 | return $this->request;
213 | }
214 |
215 | /**
216 | * {@inheritdoc}
217 | */
218 | public function setRequest(Request $request)
219 | {
220 | $this->request = $request;
221 | }
222 |
223 | /**
224 | * {@inheritdoc}
225 | */
226 | public function getStorage()
227 | {
228 | return $this->storage;
229 | }
230 |
231 | /**
232 | * {@inheritdoc}
233 | */
234 | public function setStorage(StorageInterface $storage)
235 | {
236 | $this->storage = $storage;
237 | }
238 |
239 | /**
240 | * {@inheritdoc}
241 | */
242 | public function getProgress()
243 | {
244 | $this->assertInitialized();
245 |
246 | return $this->progress;
247 | }
248 |
249 | /**
250 | * {@inheritdoc}
251 | */
252 | public function getStepHistory()
253 | {
254 | return $this->storage->get('history', []);
255 | }
256 |
257 | /**
258 | * {@inheritdoc}
259 | */
260 | public function setStepHistory(array $history)
261 | {
262 | $this->storage->set('history', $history);
263 | }
264 |
265 | /**
266 | * {@inheritdoc}
267 | */
268 | public function addStepToHistory($stepName)
269 | {
270 | $history = $this->getStepHistory();
271 | array_push($history, $stepName);
272 | $this->setStepHistory($history);
273 | }
274 |
275 | /**
276 | * {@inheritdoc}
277 | */
278 | public function rewindHistory()
279 | {
280 | $history = $this->getStepHistory();
281 |
282 | while ($top = end($history)) {
283 | if ($top !== $this->currentStep->getName()) {
284 | array_pop($history);
285 | } else {
286 | break;
287 | }
288 | }
289 |
290 | if (0 === count($history)) {
291 | throw new NotFoundHttpException(sprintf('Step "%s" not found in step history.', $this->currentStep->getName()));
292 | }
293 |
294 | $this->setStepHistory($history);
295 | }
296 |
297 | /**
298 | * {@inheritdoc}
299 | */
300 | public function setNextStepByName($stepName)
301 | {
302 | $this->nextStep = $this->process->getStepByName($stepName);
303 | }
304 |
305 | /**
306 | * If context was not initialized, throw exception.
307 | *
308 | * @throws \RuntimeException
309 | */
310 | protected function assertInitialized()
311 | {
312 | if (!$this->initialized) {
313 | throw new \RuntimeException('Process context was not initialized');
314 | }
315 | }
316 |
317 | /**
318 | * Calculates progress based on current step index.
319 | *
320 | * @param int $currentStepIndex
321 | */
322 | protected function calculateProgress($currentStepIndex)
323 | {
324 | $this->progress = floor(($currentStepIndex + 1) / $this->process->countSteps() * 100);
325 | }
326 | }
327 |
--------------------------------------------------------------------------------
/Process/Coordinator/Coordinator.php:
--------------------------------------------------------------------------------
1 |
31 | */
32 | class Coordinator implements CoordinatorInterface
33 | {
34 | /**
35 | * Router.
36 | *
37 | * @var RouterInterface
38 | */
39 | protected $router;
40 |
41 | /**
42 | * Process builder.
43 | *
44 | * @var ProcessBuilderInterface
45 | */
46 | protected $builder;
47 |
48 | /**
49 | * Process context.
50 | *
51 | * @var ProcessContextInterface
52 | */
53 | protected $context;
54 |
55 | /**
56 | * Registered scenarios.
57 | *
58 | * @var array
59 | */
60 | protected $scenarios = [];
61 |
62 | /**
63 | * Constructor.
64 | *
65 | * @param RouterInterface $router
66 | * @param ProcessBuilderInterface $builder
67 | * @param ProcessContextInterface $context
68 | */
69 | public function __construct(RouterInterface $router, ProcessBuilderInterface $builder, ProcessContextInterface $context)
70 | {
71 | $this->router = $router;
72 | $this->builder = $builder;
73 | $this->context = $context;
74 | }
75 |
76 | /**
77 | * {@inheritdoc}
78 | */
79 | public function start($scenarioAlias, ParameterBag $queryParameters = null)
80 | {
81 | $process = $this->buildProcess($scenarioAlias);
82 | $step = $process->getFirstStep();
83 |
84 | $this->context->initialize($process, $step);
85 | $this->context->close();
86 |
87 | if (!$this->context->isValid()) {
88 | return $this->processStepResult($process, $this->context->getProcess()->getValidator()->getResponse($step));
89 | }
90 |
91 | return $this->redirectToStepDisplayAction($process, $step, $queryParameters);
92 | }
93 |
94 | /**
95 | * {@inheritdoc}
96 | */
97 | public function display($scenarioAlias, $stepName, ParameterBag $queryParameters = null)
98 | {
99 | $process = $this->buildProcess($scenarioAlias);
100 | $step = $process->getStepByName($stepName);
101 |
102 | $this->context->initialize($process, $step);
103 |
104 | try {
105 | $this->context->rewindHistory();
106 | } catch (NotFoundHttpException $e) {
107 | return $this->goToLastValidStep($process, $scenarioAlias);
108 | }
109 |
110 | return $this->processStepResult(
111 | $process,
112 | $this->context->isValid() ? $step->displayAction($this->context) : $this->context->getProcess()->getValidator()->getResponse($step)
113 | );
114 | }
115 |
116 | /**
117 | * {@inheritdoc}
118 | */
119 | public function forward($scenarioAlias, $stepName)
120 | {
121 | $process = $this->buildProcess($scenarioAlias);
122 | $step = $process->getStepByName($stepName);
123 |
124 | $this->context->initialize($process, $step);
125 |
126 | try {
127 | $this->context->rewindHistory();
128 | } catch (NotFoundHttpException $e) {
129 | return $this->goToLastValidStep($process, $scenarioAlias);
130 | }
131 |
132 | return $this->processStepResult(
133 | $process,
134 | $this->context->isValid() ? $step->forwardAction($this->context) : $this->context->getProcess()->getValidator()->getResponse($step)
135 | );
136 | }
137 |
138 | /**
139 | * @param ProcessInterface $process
140 | * @param $result
141 | *
142 | * @return RedirectResponse
143 | */
144 | public function processStepResult(ProcessInterface $process, $result)
145 | {
146 | if ($result instanceof Response || $result instanceof View) {
147 | return $result;
148 | }
149 |
150 | if ($result instanceof ActionResult) {
151 | // Handle explicit jump to step.
152 | if ($result->getNextStepName()) {
153 | $this->context->setNextStepByName($result->getNextStepName());
154 |
155 | return $this->redirectToStepDisplayAction($process, $this->context->getNextStep());
156 | }
157 |
158 | // Handle last step.
159 | if ($this->context->isLastStep()) {
160 | $this->context->close();
161 |
162 | return new RedirectResponse(
163 | $this->router->generate($process->getRedirect(), $process->getRedirectParams())
164 | );
165 | }
166 |
167 | // Handle default linear behaviour.
168 | return $this->redirectToStepDisplayAction($process, $this->context->getNextStep());
169 | }
170 |
171 | throw new \RuntimeException('Wrong action result, expected Response or ActionResult');
172 | }
173 |
174 | /**
175 | * {@inheritdoc}
176 | */
177 | public function registerScenario($alias, ProcessScenarioInterface $scenario)
178 | {
179 | if (isset($this->scenarios[$alias])) {
180 | throw new InvalidArgumentException(
181 | sprintf('Process scenario with alias "%s" is already registered', $alias)
182 | );
183 | }
184 |
185 | $this->scenarios[$alias] = $scenario;
186 | }
187 |
188 | /**
189 | * {@inheritdoc}
190 | */
191 | public function loadScenario($alias)
192 | {
193 | if (!isset($this->scenarios[$alias])) {
194 | throw new InvalidArgumentException(sprintf('Process scenario with alias "%s" is not registered', $alias));
195 | }
196 |
197 | return $this->scenarios[$alias];
198 | }
199 |
200 | /**
201 | * Redirect to step display action.
202 | *
203 | * @param ProcessInterface $process
204 | * @param StepInterface $step
205 | * @param ParameterBag $queryParameters
206 | *
207 | * @return RedirectResponse
208 | */
209 | protected function redirectToStepDisplayAction(
210 | ProcessInterface $process,
211 | StepInterface $step,
212 | ParameterBag $queryParameters = null
213 | ) {
214 | $this->context->addStepToHistory($step->getName());
215 |
216 | if (null !== $route = $process->getDisplayRoute()) {
217 | $url = $this->router->generate($route, array_merge(
218 | $process->getDisplayRouteParams(),
219 | ['stepName' => $step->getName()],
220 | $queryParameters ? $queryParameters->all() : []
221 | ));
222 |
223 | return new RedirectResponse($url);
224 | }
225 |
226 | // Default parameters for display route
227 | $routeParameters = [
228 | 'scenarioAlias' => $process->getScenarioAlias(),
229 | 'stepName' => $step->getName(),
230 | ];
231 |
232 | if (null !== $queryParameters) {
233 | $routeParameters = array_merge($queryParameters->all(), $routeParameters);
234 | }
235 |
236 | return new RedirectResponse(
237 | $this->router->generate('sylius_flow_display', $routeParameters)
238 | );
239 | }
240 |
241 | /**
242 | * @param ProcessInterface $process
243 | * @param string $scenarioAlias
244 | *
245 | * @return RedirectResponse
246 | */
247 | protected function goToLastValidStep(ProcessInterface $process, $scenarioAlias)
248 | {
249 | //the step we are supposed to display was not found in the history.
250 | if (null === $this->context->getPreviousStep()) {
251 | //there is no previous step go to start
252 | return $this->start($scenarioAlias);
253 | }
254 |
255 | //we will go back to previous step...
256 | $history = $this->context->getStepHistory();
257 | if (empty($history)) {
258 | //there is no history
259 | return $this->start($scenarioAlias);
260 | }
261 | $step = $process->getStepByName(end($history));
262 |
263 | $this->context->initialize($process, $step);
264 |
265 | return $this->redirectToStepDisplayAction($process, $step);
266 | }
267 |
268 | /**
269 | * Builds process for given scenario alias.
270 | *
271 | * @param string $scenarioAlias
272 | *
273 | * @return ProcessInterface
274 | */
275 | protected function buildProcess($scenarioAlias)
276 | {
277 | $process = $this->builder->build($this->loadScenario($scenarioAlias));
278 | $process->setScenarioAlias($scenarioAlias);
279 |
280 | return $process;
281 | }
282 | }
283 |
--------------------------------------------------------------------------------