├── .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 [![Build status...](https://secure.travis-ci.org/Sylius/SyliusFlowBundle.png?branch=master)](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 | --------------------------------------------------------------------------------