├── .scrutinizer.yml ├── CHANGELOG.md ├── README.md ├── composer.json ├── example └── simple.php ├── phpspec.yml ├── spec ├── Data │ └── EntityIdSpec.php ├── Flow │ ├── BaseSpec.php │ ├── Condition │ │ ├── Transition │ │ │ ├── AndConditionSpec.php │ │ │ ├── ConditionCollectionSpec.php │ │ │ ├── OrConditionSpec.php │ │ │ └── PayloadPropertyConditionSpec.php │ │ └── Workflow │ │ │ ├── AndConditionSpec.php │ │ │ ├── ConditionCollectionSpec.php │ │ │ ├── OrConditionSpec.php │ │ │ └── ProviderNameConditionSpec.php │ ├── Context │ │ ├── ErrorCollectionSpec.php │ │ └── PropertiesSpec.php │ ├── ContextSpec.php │ ├── Exception │ │ └── ActionFailedExceptionSpec.php │ ├── ItemSpec.php │ ├── Security │ │ └── PermissionSpec.php │ ├── StateSpec.php │ ├── StepSpec.php │ ├── TransitionSpec.php │ └── WorkflowSpec.php ├── Handler │ ├── RepositoryBasedTransitionHandlerFactorySpec.php │ └── RepositoryBasedTransitionHandlerSpec.php ├── Manager │ └── WorkflowManagerSpec.php └── Util │ └── ComparisonSpec.php └── src ├── Data ├── EntityId.php ├── EntityManager.php ├── EntityRepository.php ├── Specification.php └── StateRepository.php ├── Exception ├── WorkflowException.php └── WorkflowNotFound.php ├── Flow ├── Action.php ├── Base.php ├── Condition │ ├── Transition │ │ ├── AndCondition.php │ │ ├── Condition.php │ │ ├── ConditionCollection.php │ │ ├── OrCondition.php │ │ └── PayloadPropertyCondition.php │ └── Workflow │ │ ├── AndCondition.php │ │ ├── Condition.php │ │ ├── ConditionCollection.php │ │ ├── ConfigValueCondition.php │ │ ├── OrCondition.php │ │ └── ProviderNameCondition.php ├── Context.php ├── Context │ ├── ErrorCollection.php │ └── Properties.php ├── Exception │ ├── ActionFailedException.php │ ├── FlowException.php │ ├── StepNotFoundException.php │ └── TransitionNotFound.php ├── Item.php ├── Security │ └── Permission.php ├── State.php ├── Step.php ├── Transition.php └── Workflow.php ├── Handler ├── AbstractTransitionHandler.php ├── RepositoryBasedTransitionHandler.php ├── RepositoryBasedTransitionHandlerFactory.php ├── TransitionHandler.php └── TransitionHandlerFactory.php ├── Manager ├── CachedManager.php ├── Manager.php └── WorkflowManager.php ├── Transaction ├── DelegatingTransactionHandler.php └── TransactionHandler.php └── Util └── Comparison.php /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: [src/*] 3 | excluded_paths: [vendor/*, bin/*, spec/*] 4 | tools: 5 | php_sim: 6 | min_mass: 25 7 | php_mess_detector: 8 | enabled: true 9 | php_pdepend: 10 | enabled: true 11 | php_analyzer: 12 | enabled: true 13 | external_code_coverage: 14 | timeout: 600 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | Changelog 3 | ========= 4 | 5 | [Unreleased] 6 | ------------ 7 | 8 | [2.1.1] (2020-07-01) 9 | -------------------- 10 | 11 | ### Fixed 12 | 13 | - Pass raw error array to the state 14 | 15 | [2.1.0] (2020-02-07) 16 | -------------------- 17 | 18 | ### Added 19 | 20 | - Add method execute to Transition 21 | - Item tracks state changes which can be released by Item#releaseRecordedStateChanges 22 | - Step contains the workflow name 23 | - State supports different workflow name for start and target states 24 | 25 | ### Changed 26 | 27 | - Transition can be initialized without a target step 28 | - Deprecate executeActions and executePostActions of Transition 29 | - Call Transition#execute in AbstractTransitionHandler 30 | - Use Item#releaseRecordedStateChanges in RepositoryBasedTransitionHandler to store all state changes 31 | - Transition#validate also validates post actions 32 | - Transition#getRequiredPayloadProperties also recognize options of post actions 33 | 34 | ### Fixed 35 | 36 | - Handle case that Transition#getStepTo is null 37 | 38 | [2.0.2] (2019-12-05) 39 | -------------------- 40 | 41 | ### Fixed 42 | 43 | - Fix initial value of item state 44 | 45 | [2.0.1] (2019-02-08) 46 | -------------------- 47 | 48 | ### Fixed 49 | 50 | - Fix available transitions if workflow has changed. 51 | - Fix item state history is not initialized properly which might cause an 52 | `Parameter must be an array or an object that implements Countable` error. 53 | 54 | ### Added 55 | 56 | - `ActionFailedException` might contain action name and error collection now. 57 | - `ErrorCollection` implements `Countable` now 58 | - Added `Item#getLatestStateOccurred` and `Item#getLatestSuccessfulState` 59 | 60 | ### Changed 61 | 62 | - Actions will also fail if any error is added during transition. 63 | 64 | ### Deprecated 65 | 66 | - Deprecate `Item#getLatestState`. Use `Item#getLatestStateOccurred` or `Item#getLatestSuccessfulState` state instead. 67 | 68 | 69 | [2.0.0] (2018-07-24) 70 | ------------------ 71 | 72 | 73 | [2.0.0-beta1] (2017-11-24) 74 | ------------------------ 75 | 76 | - Allow to detach an item from a workflow. 77 | - Move error collection to the context. 78 | - Drop User and Role. Just support permissions. 79 | - Change user data handling: Introduce payload instead of handling forms. 80 | - Add required parameters to the constructor for flow elements. 81 | - Add strict types (PHP 7.1). 82 | - Switch to psr-4. 83 | - Utilize phpcq/all-tasks. 84 | 85 | 86 | [Unreleased]: https://github.com/netzmacht/workfow/compare/master...develop 87 | [2.1.0]: https://github.com/netzmacht/workfow/compare/2.0.2...2.1.0 88 | [2.0.2]: https://github.com/netzmacht/workfow/compare/2.0.1...2.0.2 89 | [2.0.1]: https://github.com/netzmacht/workfow/compare/2.0.0...2.0.1 90 | [2.0.0]: https://github.com/netzmacht/workfow/compare/2.0.0-beta1...2.0.0 91 | [2.0.0-beta1]: https://github.com/netzmacht/workfow/compare/1.0.0-beta2...2.0.0-beta1 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Framework independent workflow library 3 | ====================================== 4 | 5 | [![Build Status](http://img.shields.io/travis/netzmacht/workflow/master.svg?style=flat-square)](https://travis-ci.org/netzmacht/workflow) 6 | [![Version](http://img.shields.io/packagist/v/netzmacht/workflow.svg?style=flat-square)](http://packagist.com/packages/netzmacht/workflow) 7 | [![Code quality](http://img.shields.io/scrutinizer/g/netzmacht/workflow.svg?style=flat-square)](https://scrutinizer-ci.com/g/netzmacht/workflow/) 8 | [![Code coverage](http://img.shields.io/scrutinizer/coverage/g/netzmacht/workflow.svg?style=flat-square)](https://scrutinizer-ci.com/g/netzmacht/workflow/) 9 | 10 | This is a framework independent workflow library. It provides an step-transition based workflow implementation for 11 | processing entities through its life cycle. 12 | 13 | Due to its data format and framework independence it **does not run** as a standalone workflow library. 14 | The entity/data implementation and input processing via forms have to be implemented. This workflow library is more a 15 | **skeleton** for your workflow requirements. 16 | 17 | Features 18 | -------- 19 | 20 | **The main concept** 21 | * An entity processes different steps in its lifecycle. 22 | * The process between two steps is called Transition. 23 | * A transition can depend on conditions which determine if the transition is available. 24 | * Each transition contains a list of actions which are performed to reach the next step. 25 | * Actions can require additional user input to perform the transition. 26 | * User input are handled by a form. 27 | 28 | **Workflow items** 29 | * The Item wraps the entity to provide workflow related information. 30 | * It knows the current state and the whole state history. 31 | * Due to the flexibility of the data structure the EntityId is used to identify an entity. 32 | 33 | **Worfklow** 34 | * An workflow is defined for a specific entities from a specific data provider. 35 | * The workflow is the definition of multiple steps and their transitions. 36 | * A workflow always has one start transition. 37 | * It can have multiple end transitions. 38 | 39 | **Manager** 40 | * There can be multiple workflow definitions for the same data provider. 41 | * The manager selects the matching workflow and creates the transition handler. 42 | * At the moment an item can only be in one workflow. 43 | 44 | **Permissions** 45 | * Transitions and steps can can be limited to an permission. 46 | * Checking the permission and organizing them is part of the current implementation. 47 | 48 | **More features** 49 | * Collection based repositories. 50 | * Transaction save transitions. 51 | * Flexible config system for workflows, steps and transitions. 52 | 53 | Requirements 54 | ------------ 55 | 56 | This library requires at least PHP 7.1. 57 | 58 | Changelog 59 | --------- 60 | 61 | See the [CHANGELOG.md](https://github.com/netzmacht/workflow/blob/develop/CHANGELOG.md) 62 | 63 | Example 64 | ------- 65 | 66 | You may have a look at the [examples](https://github.com/netzmacht/workflow/tree/develop/example). 67 | 68 | A concrete implementation is available as integration for CMS Contao 69 | [netzmacht/contao-workflow](https:github.com/netzmacht/contao-workflow). 70 | 71 | Credits 72 | ------- 73 | 74 | This library is heavenly inspired by the great workflow implementation of [orocrm plattform](http://github.com/orocrm/plattform) 75 | and got some concepts from the [LexikWorkflowBundle](https://github.com/lexik/LexikWorkflowBundle). 76 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netzmacht/workflow", 3 | "type": "library", 4 | "description": "Workflow library", 5 | "keywords": [ 6 | "workflow", 7 | "transition", 8 | "states" 9 | ], 10 | "license": "LGPL-3.0-or-later", 11 | "authors": [ 12 | { 13 | "name": "David Molineus", 14 | "email": "mail@netzmacht.de", 15 | "homepage": "https://netzmacht.de", 16 | "role": "Developer" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=7.1", 21 | "beberlei/assert": "^2.0 || ^3.0" 22 | }, 23 | "require-dev": { 24 | "phpcq/all-tasks": "^1.2", 25 | "phpspec/phpspec": "~5.0 || ~6.0" 26 | }, 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "2.1.x-dev", 30 | "dev-develop": "2.2.x-dev" 31 | } 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Netzmacht\\Workflow\\": "src/" 36 | } 37 | }, 38 | "support": { 39 | "email": "mail@netzmacht.de", 40 | "issues": "https://github.com/netzmacht/workflow/issues", 41 | "source": "https://github.com/netzmacht/workflow" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/simple.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2017 netzmacht David Molineus. All rights reserved. 9 | * @license LGPL-3.0 https://github.com/netzmacht/contao-leaflet-maps/blob/master/LICENSE 10 | * @filesource 11 | */ 12 | 13 | use Netzmacht\Workflow\Data\EntityId; 14 | use Netzmacht\Workflow\Data\StateRepository; 15 | use Netzmacht\Workflow\Flow\Condition\Transition\PayloadPropertyCondition; 16 | use Netzmacht\Workflow\Flow\Condition\Workflow\ProviderNameCondition; 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\State; 20 | use Netzmacht\Workflow\Flow\Step; 21 | use Netzmacht\Workflow\Flow\Transition; 22 | use Netzmacht\Workflow\Flow\Workflow; 23 | use Netzmacht\Workflow\Handler\AbstractTransitionHandler; 24 | use Netzmacht\Workflow\Handler\TransitionHandler; 25 | use Netzmacht\Workflow\Handler\TransitionHandlerFactory; 26 | use Netzmacht\Workflow\Manager\WorkflowManager; 27 | use Netzmacht\Workflow\Util\Comparison; 28 | 29 | /* 30 | * First let's create the workflow. 31 | */ 32 | 33 | $workflow = new Workflow('test', 'example'); 34 | 35 | // Our workflow should only handle the example data provider. 36 | $workflow->addCondition(new ProviderNameCondition('example')); 37 | 38 | /* 39 | * Now define and configure the steps. 40 | */ 41 | $createdStep = new Step('created'); 42 | $editedStep = new Step('edited'); 43 | 44 | // Deleted is the last step, define it. 45 | $deletedStep = new Step('deleted'); 46 | $deletedStep->setFinal(true); 47 | 48 | $workflow 49 | ->addStep($createdStep) 50 | ->addStep($editedStep) 51 | ->addStep($deletedStep); 52 | 53 | /* 54 | * Create the process by defining transitions. 55 | */ 56 | 57 | // Every transition transits to a defined step. Here $createdStep 58 | $createTransition = new Transition('create', $workflow, $createdStep); 59 | $editTransition = new Transition('edit', $workflow, $editedStep); 60 | $deleteTransition = new Transition('deleted', $workflow, $deletedStep); 61 | 62 | // Now let's define which transition is available after a step 63 | $createdStep 64 | ->allowTransition($editTransition) 65 | ->allowTransition($deleteTransition); 66 | 67 | // Circular transitions are allowed (edit -> edited -> edit) 68 | $editedStep 69 | ->allowTransition($editTransition) 70 | ->allowTransition($deleteTransition); 71 | 72 | // The workflow has get a start transition. This transition has be called at first. 73 | $workflow->setStartTransition($createTransition); 74 | 75 | /* 76 | * Our workflow is defined. One thing, it's missing. We want to require that the delete transition is confirmed. 77 | * Let's create a condition for it. 78 | */ 79 | 80 | $deleteTransition->addCondition(new PayloadPropertyCondition('confirm', true, Comparison::IDENTICAL)); 81 | 82 | /* 83 | * In a real workflow you want to define actions which are handled by each transition. 84 | * 85 | * Let's create an action which sets the current step as state to the example. 86 | */ 87 | 88 | $action = new class implements \Netzmacht\Workflow\Flow\Action { 89 | public function getRequiredPayloadProperties(Item $item): array 90 | { 91 | return []; 92 | } 93 | 94 | public function validate(Item $item, Context $context): bool 95 | { 96 | return true; 97 | } 98 | 99 | public function transit(Transition $transition, Item $item, Context $context): void 100 | { 101 | // Assume that the entity is an array object. Workflow doesn't care which format the entity has. 102 | // But your action have to be aware. 103 | $entity = $item->getEntity(); 104 | 105 | $entity['state'] = $transition->getStepTo()->getName(); 106 | } 107 | }; 108 | 109 | $createTransition->addAction($action); 110 | $editTransition->addAction($action); 111 | $deleteTransition->addAction($action); 112 | 113 | /* 114 | * The workflow is defined. To handle the workflow we register the workflow to the workflow manager. 115 | */ 116 | 117 | // As workflow doesn't care about your data, you have to handle it. For the simplify whe create a noop state repository 118 | // and a stupid transition handler. 119 | 120 | $stateRepository = new class implements StateRepository { 121 | public function find(EntityId $entityId): iterable 122 | { 123 | // We have to fetch all state in an ascending order here 124 | return []; 125 | } 126 | 127 | public function add(State $state): void 128 | { 129 | // Add a new state 130 | } 131 | }; 132 | 133 | $transitionHandler = new class($stateRepository) extends AbstractTransitionHandler { 134 | public function transit(): State 135 | { 136 | $state = $this->executeTransition(); 137 | 138 | // We actually have to store the state in a state repository now. 139 | // We could store the item in an repository as well now. 140 | 141 | return $state; 142 | } 143 | }; 144 | 145 | $transitionHandlerFactory = new class($transitionHandler) implements TransitionHandlerFactory { 146 | /** 147 | * @var TransitionHandler 148 | */ 149 | private $transitionHandler; 150 | 151 | public function __construct(TransitionHandler $transitionHandler) 152 | { 153 | $this->transitionHandler = $transitionHandler; 154 | } 155 | 156 | public function createTransitionHandler( 157 | Item $item, 158 | Workflow $workflow, 159 | ?string $transitionName, 160 | string $providerName, 161 | StateRepository $stateRepository 162 | ): TransitionHandler { 163 | return $this->transitionHandler; 164 | } 165 | }; 166 | 167 | // Now everything is prepared. The workflow manager decides which workflow to use. 168 | $manager = new WorkflowManager($transitionHandlerFactory, $stateRepository, [$workflow]); 169 | 170 | /* 171 | * Handle an item. 172 | */ 173 | 174 | $entityId = EntityId::fromProviderNameAndId('example', 1); 175 | $entity = new ArrayObject(['state' => null]); 176 | 177 | // Create a workflow item, which knows it current state 178 | $item = $manager->createItem($entityId, $entity); 179 | $handler = $manager->handle($item, 'start'); 180 | 181 | // If workflow which supports the entity is found, null is returned. 182 | if ($handler) { 183 | $payload = []; 184 | 185 | // if some actions require some payload, you get the required properties here. 186 | if ($handler->getRequiredPayloadProperties()) { 187 | // We don't have such actions. Just leave it empty 188 | } 189 | 190 | // We have to validate the handler first. All conditions are checked. 191 | if ($handler->validate($payload)) { 192 | // Finally let's transit to the next state. 193 | $state = $handler->transit(); 194 | } else { 195 | $errors = $handler->getContext()->getErrorCollection(); 196 | // Display the errors. 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | formatter.name: pretty 2 | suites: 3 | worfklow_suite: 4 | namespace: Netzmacht\Workflow 5 | psr4_prefix: Netzmacht\Workflow 6 | -------------------------------------------------------------------------------- /spec/Data/EntityIdSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Data; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | /** 19 | * Class EntityIdSpec 20 | * 21 | * @package spec\Netzmacht\Workflow\Data 22 | */ 23 | class EntityIdSpec extends ObjectBehavior 24 | { 25 | const PROVIDER_NAME = 'provider_example'; 26 | 27 | const IDENTIFIER = 10; 28 | 29 | function let() 30 | { 31 | $this->beConstructedThrough('fromProviderNameAndId', [static::PROVIDER_NAME, static::IDENTIFIER]); 32 | } 33 | 34 | function it_is_initializable() 35 | { 36 | $this->shouldHaveType(EntityId::class); 37 | } 38 | 39 | function it_has_an_identifier() 40 | { 41 | $this->getIdentifier() 42 | ->shouldReturn(static::IDENTIFIER); 43 | } 44 | 45 | function it_has_a_provider_name() 46 | { 47 | $this->getProviderName() 48 | ->shouldReturn(static::PROVIDER_NAME); 49 | } 50 | 51 | function it_equals_to_same_entity_id() 52 | { 53 | $otherEntityId = EntityId::fromString(static::PROVIDER_NAME . '::' . static::IDENTIFIER); 54 | 55 | $this->equals($otherEntityId) 56 | ->shouldReturn(true); 57 | } 58 | 59 | function it_does_not_equals_to_another_entity_id_with_different_id() 60 | { 61 | $otherEntityId = EntityId::fromString(static::PROVIDER_NAME . '::' . (static::IDENTIFIER + 5)); 62 | 63 | $this->equals($otherEntityId) 64 | ->shouldReturn(false); 65 | } 66 | 67 | function it_does_not_equals_to_another_entity_id_with_different_provider_name() 68 | { 69 | $otherEntityId = EntityId::fromString(static::PROVIDER_NAME . '_2::' . static::IDENTIFIER); 70 | 71 | $this->equals($otherEntityId) 72 | ->shouldReturn(false); 73 | } 74 | 75 | function it_casts_to_string() 76 | { 77 | $this->__toString() 78 | ->shouldReturn(static::PROVIDER_NAME . '::' . static::IDENTIFIER); 79 | } 80 | 81 | function it_parses_string_representation() 82 | { 83 | $this->beConstructedThrough('fromString', [static::PROVIDER_NAME . '::' . static::IDENTIFIER]); 84 | 85 | $this->getIdentifier() 86 | ->shouldReturn(static::IDENTIFIER); 87 | 88 | $this->getProviderName() 89 | ->shouldReturn(static::PROVIDER_NAME); 90 | } 91 | 92 | function it_constructs_from_scalars() 93 | { 94 | $this->beConstructedThrough('fromProviderNameAndId', [static::PROVIDER_NAME, static::IDENTIFIER]); 95 | 96 | $this->getIdentifier() 97 | ->shouldReturn(static::IDENTIFIER); 98 | 99 | $this->getProviderName() 100 | ->shouldReturn(static::PROVIDER_NAME); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /spec/Flow/BaseSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow; 14 | 15 | use Netzmacht\Workflow\Flow\Base; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | /** 19 | * Class BaseSpec 20 | * 21 | * @package spec\Netzmacht\Workflow 22 | */ 23 | class BaseSpec extends ObjectBehavior 24 | { 25 | const NAME = 'test'; 26 | const LABEL = 'label'; 27 | const ID = 5; 28 | 29 | function let() 30 | { 31 | $this->beAnInstanceOf(BaseExample::class); 32 | $this->beConstructedWith(static::NAME); 33 | } 34 | 35 | function it_is_initializable() 36 | { 37 | $this->shouldHaveType(Base::class); 38 | } 39 | 40 | function it_accepts_initial_config() 41 | { 42 | $this->beConstructedWith(static::NAME, '', ['config' => 'test']); 43 | $this->getConfig()->shouldBe(['config' => 'test']); 44 | } 45 | 46 | function it_accepts_initial_label() 47 | { 48 | $this->beConstructedWith(static::NAME, static::LABEL); 49 | $this->getLabel()->shouldBe(static::LABEL); 50 | } 51 | 52 | function it_has_a_name() 53 | { 54 | $this->getName()->shouldReturn(static::NAME); 55 | } 56 | 57 | function it_has_a_label() 58 | { 59 | $this->setLabel(static::LABEL)->shouldReturn($this); 60 | $this->getLabel()->shouldReturn(static::LABEL); 61 | } 62 | 63 | function it_uses_name_as_label_if_no_label_given() 64 | { 65 | $this->getLabel()->shouldReturn(static::NAME); 66 | } 67 | 68 | function it_has_config_values() 69 | { 70 | $this->hasConfigValue('config')->shouldReturn(false); 71 | $this->setConfigValue('config', 'test')->shouldReturn($this); 72 | $this->getConfigValue('config')->shouldReturn('test'); 73 | $this->hasConfigValue('config')->shouldReturn(true); 74 | } 75 | 76 | function it_accepts_an_default_value_for_nonexisting_config_values() 77 | { 78 | $this->getConfigValue('config', 'bar')->shouldReturn('bar'); 79 | } 80 | 81 | function it_adds_multiple_config_values() 82 | { 83 | $this->addConfig(['config' => 'foo', 'test' => 'bar'])->shouldReturn($this); 84 | $this->getConfigValue('config')->shouldReturn('foo'); 85 | $this->getConfigValue('test')->shouldReturn('bar'); 86 | } 87 | 88 | function it_removes_a_config_value() 89 | { 90 | $this->setConfigValue('config', 'test'); 91 | $this->hasConfigValue('config')->shouldReturn(true); 92 | $this->removeConfigValue('config')->shouldReturn($this); 93 | $this->hasConfigValue('config')->shouldReturn(false); 94 | } 95 | 96 | function it_returns_config() 97 | { 98 | $this->setConfigValue('config', 'test'); 99 | $this->getConfig()->shouldBe(['config' => 'test']); 100 | } 101 | } 102 | 103 | class BaseExample extends Base 104 | { 105 | 106 | } 107 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Transition/AndConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Transition; 14 | 15 | use Netzmacht\Workflow\Flow\Condition\Transition\Condition; 16 | use Netzmacht\Workflow\Flow\Context; 17 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | use PhpSpec\ObjectBehavior; 21 | use Prophecy\Argument; 22 | 23 | /** 24 | * Class AndConditionSpec 25 | * 26 | * @package spec\Netzmacht\Workflow\Flow\Condition\Transition 27 | */ 28 | class AndConditionSpec extends ObjectBehavior 29 | { 30 | const ERROR_COLLECTION_CLASS = 'Netzmacht\Workflow\Flow\Context\ErrorCollection'; 31 | 32 | function it_is_initializable() 33 | { 34 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Transition\AndCondition'); 35 | } 36 | 37 | function it_is_a_condition_collection() 38 | { 39 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Transition\ConditionCollection'); 40 | } 41 | 42 | function it_matches_if_all_children_matches( 43 | Condition $conditionA, 44 | Condition $conditionB, 45 | Transition $transition, 46 | Item $item, 47 | Context $context, 48 | ErrorCollection $errorCollection 49 | ) { 50 | $context->createCleanCopy()->willReturn($context); 51 | $context->getErrorCollection()->willReturn($errorCollection); 52 | 53 | $conditionA->match($transition, $item, $context)->willReturn(true); 54 | $conditionB->match($transition, $item, $context)->willReturn(true); 55 | 56 | $this->addCondition($conditionA); 57 | $this->addCondition($conditionB); 58 | 59 | $this->match($transition, $item, $context)->shouldReturn(true); 60 | } 61 | 62 | function it_does_not_match_if_one_child_does_not( 63 | Condition $conditionA, 64 | Condition $conditionB, 65 | Transition $transition, 66 | Item $item, 67 | Context $context, 68 | ErrorCollection $errorCollection 69 | ) { 70 | $context->createCleanCopy()->willReturn($context); 71 | $context->getErrorCollection()->willReturn($errorCollection); 72 | 73 | $conditionA->match($transition, $item, $context)->willReturn(true); 74 | $conditionB->match($transition, $item, $context)->willReturn(false); 75 | 76 | $context->addError(Argument::cetera())->shouldBeCalled(); 77 | 78 | $this->addCondition($conditionA); 79 | $this->addCondition($conditionB); 80 | 81 | $this->match($transition, $item, $context)->shouldReturn(false); 82 | } 83 | 84 | function it_matches_if_no_children_exists( 85 | Transition $transition, 86 | Item $item, 87 | Context $context, 88 | ErrorCollection $errorCollection 89 | ) { 90 | $context->createCleanCopy()->willReturn($context); 91 | $context->getErrorCollection()->willReturn($errorCollection); 92 | 93 | $this->match($transition, $item, $context)->shouldReturn(true); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Transition/ConditionCollectionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Transition; 14 | 15 | use Netzmacht\Workflow\Flow\Condition\Transition\Condition; 16 | use Netzmacht\Workflow\Flow\Condition\Transition\ConditionCollection as AbstractConditionCollection; 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | use PhpSpec\ObjectBehavior; 21 | 22 | /** 23 | * Class ConditionCollectionSpec 24 | * 25 | * @package spec\Netzmacht\Workflow\Flow\Condition\Transition 26 | */ 27 | class ConditionCollectionSpec extends ObjectBehavior 28 | { 29 | function let() 30 | { 31 | $this->beAnInstanceOf('spec\Netzmacht\Workflow\Flow\Condition\Transition\ConditionCollection'); 32 | } 33 | 34 | function it_is_initializable() 35 | { 36 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Transition\ConditionCollection'); 37 | } 38 | 39 | 40 | function it_adds_condition(Condition $condition) 41 | { 42 | $this->addCondition($condition)->shouldReturn($this); 43 | $this->getConditions()->shouldReturn([$condition]); 44 | } 45 | 46 | function it_adds_conditions(Condition $condition) 47 | { 48 | $this->addConditions([$condition])->shouldReturn($this); 49 | $this->getConditions()->shouldReturn([$condition]); 50 | } 51 | 52 | function it_removes_a_condition(Condition $condition) 53 | { 54 | $this->addCondition($condition); 55 | $this->getConditions()->shouldReturn([$condition]); 56 | $this->removeCondition($condition)->shouldReturn($this); 57 | $this->getConditions()->shouldReturn([]); 58 | } 59 | 60 | function it_throws_if_invalid_condition_passed() 61 | { 62 | $this->shouldThrow('Assert\InvalidArgumentException')->duringAddConditions(['test']); 63 | } 64 | } 65 | 66 | class ConditionCollection extends AbstractConditionCollection 67 | { 68 | public function match(Transition $transition, Item $item, Context $context): bool 69 | { 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Transition/OrConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Transition; 14 | 15 | use Netzmacht\Workflow\Flow\Condition\Transition\Condition; 16 | use Netzmacht\Workflow\Flow\Context; 17 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | use PhpSpec\ObjectBehavior; 21 | use Prophecy\Argument; 22 | 23 | /** 24 | * Class OrConditionSpec 25 | * 26 | * @package spec\Netzmacht\Workflow\Flow\Condition\Transition 27 | */ 28 | class OrConditionSpec extends ObjectBehavior 29 | { 30 | const ERROR_COLLECTION_CLASS = 'Netzmacht\Workflow\Flow\Context\ErrorCollection'; 31 | 32 | function it_is_initializable() 33 | { 34 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Transition\OrCondition'); 35 | } 36 | 37 | function it_is_a_condition_collection() 38 | { 39 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Transition\ConditionCollection'); 40 | } 41 | 42 | function it_matches_if_any_child_matches( 43 | Condition $conditionA, 44 | Condition $conditionB, 45 | Transition $transition, 46 | Item $item, 47 | Context $context, 48 | ErrorCollection $errorCollection 49 | ) { 50 | $context->createCleanCopy()->willReturn($context); 51 | $context->getErrorCollection()->willReturn($errorCollection); 52 | 53 | $conditionA->match($transition, $item, $context)->willReturn(false); 54 | $conditionB->match($transition, $item, $context)->willReturn(true); 55 | 56 | $this->addCondition($conditionA); 57 | $this->addCondition($conditionB); 58 | 59 | $this->match($transition, $item, $context)->shouldReturn(true); 60 | } 61 | 62 | function it_does_not_match_if_all_children_does_not( 63 | Condition $conditionA, 64 | Condition $conditionB, 65 | Transition $transition, 66 | Item $item, 67 | Context $context, 68 | ErrorCollection $errorCollection 69 | ) { 70 | $context->createCleanCopy()->willReturn($context); 71 | $context->getErrorCollection()->willReturn($errorCollection); 72 | 73 | $conditionA->match($transition, $item, $context)->willReturn(false); 74 | $conditionB->match($transition, $item, $context)->willReturn(false); 75 | 76 | $this->addCondition($conditionA); 77 | $this->addCondition($conditionB); 78 | 79 | $context->addError(Argument::cetera())->shouldBeCalled(); 80 | 81 | $this->match($transition, $item, $context)->shouldReturn(false); 82 | } 83 | 84 | function it_matches_if_no_children_exists( 85 | Transition $transition, 86 | Item $item, 87 | Context $context, 88 | ErrorCollection $errorCollection 89 | ) { 90 | $this->match($transition, $item, $context, $errorCollection)->shouldReturn(true); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Transition/PayloadPropertyConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2017 netzmacht David Molineus. All rights reserved. 9 | * @license LGPL-3.0 https://github.com/netzmacht/contao-leaflet-maps/blob/master/LICENSE 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Transition; 14 | 15 | use Netzmacht\Workflow\Flow\Condition\Transition\Condition; 16 | use Netzmacht\Workflow\Flow\Condition\Transition\PayloadPropertyCondition; 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Context\Properties; 19 | use Netzmacht\Workflow\Flow\Item; 20 | use Netzmacht\Workflow\Flow\Transition; 21 | use Netzmacht\Workflow\Util\Comparison; 22 | use PhpSpec\ObjectBehavior; 23 | use Prophecy\Argument; 24 | 25 | /** 26 | * Class PayloadPropertyConditionSpec 27 | * 28 | * @package spec\Netzmacht\Workflow\Flow\Condition\Transition 29 | */ 30 | class PayloadPropertyConditionSpec extends ObjectBehavior 31 | { 32 | function let(Context $context, Properties $payload) 33 | { 34 | $context->getPayload()->willReturn($payload); 35 | 36 | $this->beConstructedWith('foo', 'bar'); 37 | } 38 | 39 | function it_is_initializable() 40 | { 41 | $this->shouldHaveType(PayloadPropertyCondition::class); 42 | } 43 | 44 | function it_is_a_transition_condition() 45 | { 46 | $this->shouldImplement(Condition::class); 47 | } 48 | 49 | function it_compares_payload_property_with_expected_value( 50 | Transition $transition, 51 | Item $item, 52 | Context $context, 53 | Properties $payload 54 | ) { 55 | $payload->get('foo')->willReturn('bar'); 56 | 57 | $this->match($transition, $item, $context); 58 | } 59 | 60 | function it_supports_different_operators( 61 | Transition $transition, 62 | Item $item, 63 | Context $context, 64 | Properties $payload 65 | ) { 66 | $this->beConstructedWith('foo', 3, Comparison::LESSER_THAN); 67 | $payload->get('foo')->willReturn(2); 68 | $this->match($transition, $item, $context); 69 | } 70 | 71 | function it_creates_an_error_when_comparison_fails( 72 | Transition $transition, 73 | Item $item, 74 | Context $context, 75 | Properties $payload 76 | ) { 77 | $payload->get('foo')->willReturn('baz'); 78 | 79 | $context->addError('transition.condition.payload_property.failed', Argument::type('array')) 80 | ->shouldBeCalled(); 81 | 82 | $this->match($transition, $item, $context); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Workflow/AndConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Workflow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Condition\Workflow\Condition; 17 | use Netzmacht\Workflow\Flow\Workflow; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * Class AndConditionSpec 22 | * 23 | * @package spec\Netzmacht\Workflow\Flow\Condition\Workflow 24 | */ 25 | class AndConditionSpec extends ObjectBehavior 26 | { 27 | protected static $entity = ['id' => 5]; 28 | 29 | function it_is_initializable() 30 | { 31 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\AndCondition'); 32 | } 33 | 34 | function it_is_a_condition_collection() 35 | { 36 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\ConditionCollection'); 37 | } 38 | 39 | function it_matches_if_all_children_matches( 40 | Condition $conditionA, 41 | Condition $conditionB, 42 | Workflow $workflow 43 | ) { 44 | $entityId = EntityId::fromProviderNameAndId('test', 5); 45 | 46 | $conditionA->match($workflow, $entityId, static::$entity)->willReturn(true); 47 | $conditionB->match($workflow, $entityId, static::$entity)->willReturn(true); 48 | 49 | $this->addCondition($conditionA); 50 | $this->addCondition($conditionB); 51 | 52 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 53 | } 54 | 55 | function it_does_not_match_if_one_child_does_not( 56 | Condition $conditionA, 57 | Condition $conditionB, 58 | Workflow $workflow 59 | ) { 60 | $entityId = EntityId::fromProviderNameAndId('test', 5); 61 | 62 | $conditionA->match($workflow, $entityId, static::$entity)->willReturn(true); 63 | $conditionB->match($workflow, $entityId, static::$entity)->willReturn(false); 64 | 65 | $this->addCondition($conditionA); 66 | $this->addCondition($conditionB); 67 | 68 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(false); 69 | } 70 | 71 | function it_matches_if_no_children_exists(Workflow $workflow) 72 | { 73 | $entityId = EntityId::fromProviderNameAndId('test', 5); 74 | 75 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Workflow/ConditionCollectionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Workflow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Condition\Workflow\Condition; 17 | use Netzmacht\Workflow\Flow\Condition\Workflow\ConditionCollection as AbstractConditionCollection; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | use PhpSpec\ObjectBehavior; 20 | 21 | /** 22 | * Class ConditionCollectionSpec 23 | * 24 | * @package spec\Netzmacht\Workflow\Flow\Condition\Workflow 25 | */ 26 | class ConditionCollectionSpec extends ObjectBehavior 27 | { 28 | function let() 29 | { 30 | $this->beAnInstanceOf('spec\Netzmacht\Workflow\Flow\Condition\Workflow\ConditionCollection'); 31 | } 32 | 33 | function it_is_initializable() 34 | { 35 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\ConditionCollection'); 36 | } 37 | 38 | function it_adds_condition(Condition $condition) 39 | { 40 | $this->addCondition($condition)->shouldReturn($this); 41 | $this->getConditions()->shouldReturn([$condition]); 42 | } 43 | 44 | function it_adds_conditions(Condition $condition) 45 | { 46 | $this->addConditions([$condition])->shouldReturn($this); 47 | $this->getConditions()->shouldReturn([$condition]); 48 | } 49 | 50 | function it_removes_a_condition(Condition $condition) 51 | { 52 | $this->addCondition($condition); 53 | $this->getConditions()->shouldReturn([$condition]); 54 | $this->removeCondition($condition)->shouldReturn($this); 55 | $this->getConditions()->shouldReturn([]); 56 | } 57 | 58 | function it_throws_if_invalid_condition_passed() 59 | { 60 | $this->shouldThrow('Assert\InvalidArgumentException')->duringAddConditions(['test']); 61 | } 62 | } 63 | 64 | class ConditionCollection extends AbstractConditionCollection 65 | { 66 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool 67 | { 68 | return false; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Workflow/OrConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Workflow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Condition\Workflow\Condition; 17 | use Netzmacht\Workflow\Flow\Workflow; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * Class OrConditionSpec 22 | * 23 | * @package spec\Netzmacht\Workflow\Flow\Condition\Workflow 24 | */ 25 | class OrConditionSpec extends ObjectBehavior 26 | { 27 | protected static $entity = ['id' => 4]; 28 | 29 | function it_is_initializable() 30 | { 31 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\OrCondition'); 32 | } 33 | 34 | function it_is_a_condition_collection() 35 | { 36 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\ConditionCollection'); 37 | } 38 | 39 | function it_matches_if_one_child_matches( 40 | Condition $conditionA, 41 | Condition $conditionB, 42 | Workflow $workflow 43 | ) { 44 | $entityId = EntityId::fromProviderNameAndId('test', 5); 45 | 46 | $conditionA->match($workflow, $entityId, static::$entity)->willReturn(false); 47 | $conditionB->match($workflow, $entityId, static::$entity)->willReturn(true); 48 | 49 | $this->addCondition($conditionA); 50 | $this->addCondition($conditionB); 51 | 52 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 53 | } 54 | 55 | function it_does_not_match_if_no_child_matches( 56 | Condition $conditionA, 57 | Condition $conditionB, 58 | Workflow $workflow 59 | ) { 60 | $entityId = EntityId::fromProviderNameAndId('test', 5); 61 | 62 | $conditionA->match($workflow, $entityId, static::$entity)->willReturn(false); 63 | $conditionB->match($workflow, $entityId, static::$entity)->willReturn(false); 64 | 65 | $this->addCondition($conditionA); 66 | $this->addCondition($conditionB); 67 | 68 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(false); 69 | } 70 | 71 | function it_matches_if_no_children_exists(Workflow $workflow) 72 | { 73 | $entityId = EntityId::fromProviderNameAndId('test', 5); 74 | 75 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /spec/Flow/Condition/Workflow/ProviderNameConditionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Condition\Workflow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Workflow; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | /** 20 | * Class ProviderTypeConditionSpec 21 | * 22 | * @package spec\Netzmacht\Workflow\Flow\Condition\Workflow 23 | */ 24 | class ProviderNameConditionSpec extends ObjectBehavior 25 | { 26 | protected static $entity = ['id' => 5]; 27 | 28 | function let() 29 | { 30 | $this->beConstructedWith('test'); 31 | } 32 | 33 | function it_is_initializable() 34 | { 35 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\ProviderNameCondition'); 36 | $this->shouldImplement('Netzmacht\Workflow\Flow\Condition\Workflow\Condition'); 37 | } 38 | 39 | function it_has_a_configurable_provider_name() 40 | { 41 | $this->getProviderName()->shouldReturn('test'); 42 | } 43 | 44 | function it_matches_against_configurabled_provider_name(Workflow $workflow) 45 | { 46 | $entityId = EntityId::fromProviderNameAndId('test', 5); 47 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 48 | 49 | $entityId = EntityId::fromProviderNameAndId('test2', 5); 50 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(false); 51 | } 52 | 53 | function it_matches_against_workflow_provider_name(Workflow $workflow) 54 | { 55 | $entityId = EntityId::fromProviderNameAndId('test', 5); 56 | $workflow->getProviderName()->willReturn('test'); 57 | 58 | $this->match($workflow, $entityId, static::$entity)->shouldReturn(true); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /spec/Flow/Context/ErrorCollectionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Context; 14 | 15 | use Countable; 16 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | class ErrorCollectionSpec extends ObjectBehavior 20 | { 21 | const MESSAGE = 'test %s %s'; 22 | 23 | protected static $params = ['foo', 'baar']; 24 | 25 | function it_is_initializable() 26 | { 27 | $this->shouldHaveType(ErrorCollection::class); 28 | } 29 | 30 | public function it_is_countable(): void 31 | { 32 | $this->shouldImplement(Countable::class); 33 | } 34 | 35 | function it_adds_error() 36 | { 37 | $this->addError(static::MESSAGE, static::$params)->shouldReturn($this); 38 | $this->getErrors()->shouldContain([static::MESSAGE, static::$params, null]); 39 | } 40 | 41 | function it_counts_errors() 42 | { 43 | $this->countErrors()->shouldReturn(0); 44 | $this->count()->shouldReturn(0); 45 | $this->addError(static::MESSAGE, static::$params); 46 | $this->countErrors()->shouldReturn(1); 47 | $this->count()->shouldReturn(1); 48 | $this->addError(static::MESSAGE, static::$params); 49 | $this->countErrors()->shouldReturn(2); 50 | $this->count()->shouldReturn(2); 51 | } 52 | 53 | function it_gets_error_by_index() 54 | { 55 | $this->addError(static::MESSAGE, static::$params); 56 | $this->getError(0)->shouldReturn([static::MESSAGE, static::$params, null]); 57 | } 58 | 59 | function it_throws_when_unknown_error_index_given() 60 | { 61 | $this->shouldThrow('InvalidArgumentException')->during('getError', [0]); 62 | } 63 | 64 | function it_can_be_reset() 65 | { 66 | $this->addError(static::MESSAGE, static::$params); 67 | $this->hasErrors()->shouldReturn(true); 68 | $this->reset()->shouldReturn($this); 69 | $this->hasErrors()->shouldReturn(false); 70 | } 71 | 72 | function it_adds_multiple_errors(ErrorCollection $errorCollection) 73 | { 74 | $errors = [ 75 | [static::MESSAGE, static::$params, null], 76 | [static::MESSAGE, static::$params, $errorCollection], 77 | ]; 78 | 79 | $allErrors = [ 80 | [static::MESSAGE, static::$params, null], 81 | [static::MESSAGE, static::$params, null], 82 | [static::MESSAGE, static::$params, $errorCollection], 83 | ]; 84 | 85 | // make sure it does not override 86 | $this->addError(static::MESSAGE, static::$params); 87 | 88 | $this->addErrors($errors)->shouldReturn($this); 89 | $this->countErrors()->shouldReturn(3); 90 | $this->getErrors()->shouldReturn($allErrors); 91 | } 92 | 93 | function it_iterates_over_errors() 94 | { 95 | $this->shouldHaveType('IteratorAggregate'); 96 | $this->getIterator()->shouldHaveType('Traversable'); 97 | } 98 | 99 | function it_converts_to_array(ErrorCollection $errorCollection) 100 | { 101 | $errors = [ 102 | [static::MESSAGE, static::$params, null], 103 | [static::MESSAGE, static::$params, $errorCollection], 104 | ]; 105 | 106 | $errorCollection->toArray() 107 | ->shouldBeCalled() 108 | ->willReturn([[static::MESSAGE, static::$params, null]]); 109 | 110 | $this->addErrors($errors)->shouldReturn($this); 111 | 112 | $this->toArray()->shouldReturn( 113 | [ 114 | [static::MESSAGE, static::$params, null], 115 | [ 116 | static::MESSAGE, 117 | static::$params, 118 | [ 119 | [static::MESSAGE, static::$params, null], 120 | ], 121 | ], 122 | ] 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /spec/Flow/Context/PropertiesSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Context; 14 | 15 | use Netzmacht\Workflow\Flow\Context\Properties; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | class PropertiesSpec extends ObjectBehavior 19 | { 20 | function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Properties::class); 23 | } 24 | 25 | function it_gets_a_property_value() 26 | { 27 | $this->set('foo', 'bar')->shouldReturn($this); 28 | $this->get('foo')->shouldReturn('bar'); 29 | } 30 | 31 | function it_knows_if_an_property_exist() 32 | { 33 | $this->has('foo')->shouldReturn(false); 34 | 35 | $this->set('foo', 'bar')->shouldReturn($this); 36 | $this->has('foo')->shouldReturn(true); 37 | } 38 | 39 | function it_gets_null_if_property_not_exist() 40 | { 41 | $this->has('foo')->shouldReturn(false); 42 | $this->get('foo')->shouldReturn(null); 43 | } 44 | 45 | function it_converts_to_array() 46 | { 47 | $this->set('foo', 'bar'); 48 | $this->toArray()->shouldReturn(['foo' => 'bar']); 49 | } 50 | 51 | function it_accepts_properties_when_being_constructed() 52 | { 53 | $this->beConstructedWith(['foo' => 'bar']); 54 | $this->toArray()->shouldReturn(['foo' => 'bar']); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/Flow/ContextSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow; 14 | 15 | use Netzmacht\Workflow\Flow\Context; 16 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 17 | use Netzmacht\Workflow\Flow\Context\Properties; 18 | use PhpSpec\ObjectBehavior; 19 | 20 | /** 21 | * Class ContextSpec 22 | * 23 | * @package spec\Netzmacht\Contao\Workflow\Flow 24 | */ 25 | class ContextSpec extends ObjectBehavior 26 | { 27 | const CUSTOM_NS = 'custom'; 28 | 29 | function it_is_initializable() 30 | { 31 | $this->shouldHaveType(Context::class); 32 | } 33 | 34 | function it_accepts_initial_properties(Properties $properties) 35 | { 36 | $this->beConstructedWith($properties); 37 | 38 | $this->getProperties()->shouldBe($properties); 39 | } 40 | 41 | function it_accepts_initial_payload(Properties $payload) 42 | { 43 | $this->beConstructedWith(null, $payload); 44 | 45 | $this->getPayload()->shouldBe($payload); 46 | } 47 | 48 | function it_has_properties() 49 | { 50 | $this->getProperties()->shouldHaveType(Properties::class); 51 | } 52 | 53 | function it_has_payload() 54 | { 55 | $this->getPayload()->shouldHaveType(Properties::class); 56 | } 57 | 58 | function it_has_error_collection() 59 | { 60 | $this->getErrorCollection()->shouldHaveType(ErrorCollection::class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spec/Flow/Exception/ActionFailedExceptionSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(ActionFailedException::class); 21 | } 22 | 23 | public function it_is_a_flow_exception(): void 24 | { 25 | $this->shouldBeAnInstanceOf(FlowException::class); 26 | } 27 | 28 | public function it_has_no_action_name_by_default(): void 29 | { 30 | $this->actionName()->shouldReturn(null); 31 | } 32 | 33 | public function it_has_no_error_collection_by_default(): void 34 | { 35 | $this->errorCollection()->shouldReturn(null); 36 | } 37 | 38 | public function it_is_instantiable_with_action_name(): void 39 | { 40 | $this->beConstructedThrough('namedAction', ['foo']); 41 | 42 | $this->getMessage()->shouldReturn('Execution of action "foo" failed.'); 43 | $this->actionName()->shouldReturn('foo'); 44 | } 45 | 46 | public function it_is_instantiable_with_action(Action $action): void 47 | { 48 | $parts = explode('\\', trim(get_class($action->getWrappedObject()), '\\')); 49 | $actionName = end($parts); 50 | 51 | $this->beConstructedThrough('action', [$action]); 52 | 53 | $this->getMessage()->shouldReturn('Execution of action "' . $actionName . '" failed.'); 54 | $this->actionName()->shouldReturn($actionName); 55 | } 56 | 57 | public function it_is_instantiable_with_labelled_action(): void 58 | { 59 | $action = new class('Foo', 'foo') extends Base implements Action 60 | { 61 | public function getRequiredPayloadProperties(Item $item): array 62 | { 63 | return []; 64 | } 65 | 66 | public function validate(Item $item, Context $context): bool 67 | { 68 | return true; 69 | } 70 | 71 | public function transit(Transition $transition, Item $item, Context $context): void 72 | { 73 | 74 | } 75 | }; 76 | 77 | $this->beConstructedThrough('action', [$action]); 78 | 79 | $this->getMessage()->shouldReturn('Execution of action "foo" failed.'); 80 | $this->actionName()->shouldReturn('foo'); 81 | } 82 | 83 | public function it_allows_error_collection_when_instantiated_with_named_action(ErrorCollection $collection): void 84 | { 85 | $this->beConstructedThrough('namedAction', ['foo', $collection]); 86 | 87 | $this->errorCollection()->shouldReturn($collection); 88 | } 89 | 90 | public function it_allows_error_collection_when_instantiated_with_action( 91 | Action $action, 92 | ErrorCollection $collection 93 | ): void { 94 | $this->beConstructedThrough('action', [$action, $collection]); 95 | 96 | $this->errorCollection()->shouldReturn($collection); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /spec/Flow/ItemSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Context; 17 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 18 | use Netzmacht\Workflow\Flow\Context\Properties; 19 | use Netzmacht\Workflow\Flow\State; 20 | use Netzmacht\Workflow\Flow\Step; 21 | use Netzmacht\Workflow\Flow\Transition; 22 | use Netzmacht\Workflow\Flow\Workflow; 23 | use PhpSpec\ObjectBehavior; 24 | use Prophecy\Argument; 25 | 26 | /** 27 | * Class ItemSpec 28 | * 29 | * @package spec\Netzmacht\Workflow\Flow 30 | */ 31 | class ItemSpec extends ObjectBehavior 32 | { 33 | protected static $entity = ['id' => 5]; 34 | 35 | /** 36 | * @var EntityId 37 | */ 38 | private $entityId; 39 | 40 | function let() 41 | { 42 | $this->entityId = EntityId::fromProviderNameAndId('entity', 4); 43 | 44 | $this->beConstructedThrough('initialize', [$this->entityId, static::$entity]); 45 | } 46 | 47 | function it_is_initializable() 48 | { 49 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Item'); 50 | } 51 | 52 | function it_restores_state_history(State $state) 53 | { 54 | $this->beConstructedThrough('reconstitute', [$this->entityId, static::$entity, [$state]]); 55 | } 56 | 57 | function it_has_an_entity_id() 58 | { 59 | $this->getEntityId()->shouldReturn($this->entityId); 60 | } 61 | 62 | function it_has_an_entity() 63 | { 64 | $this->getEntity()->shouldReturn(static::$entity); 65 | } 66 | 67 | function it_knows_if_workflow_is_started() 68 | { 69 | $this->isWorkflowStarted()->shouldReturn(false); 70 | } 71 | 72 | function it_transits_to_a_successful_state( 73 | State $state, 74 | State $newState, 75 | Transition $transition, 76 | Context $context, 77 | ErrorCollection $errorCollection 78 | ) { 79 | $state->getStepName()->willReturn('start'); 80 | $state->getWorkflowName()->willReturn('workflow_name'); 81 | $state->isSuccessful()->willReturn(true); 82 | $state->transit(Argument::cetera())->willReturn($newState); 83 | 84 | $newState->getWorkflowName()->willReturn('workflow_name'); 85 | $newState->getStepName()->willReturn('target'); 86 | $newState->isSuccessful()->willReturn(true); 87 | 88 | $this->it_restores_state_history($state); 89 | 90 | $this->transit($transition, $context, true); 91 | 92 | $this->getCurrentStepName()->shouldReturn('target'); 93 | $this->getWorkflowName()->shouldReturn('workflow_name'); 94 | $this->getStateHistory()->shouldReturn([$state, $newState]); 95 | 96 | $this->getLatestState()->shouldHaveType('Netzmacht\Workflow\Flow\State'); 97 | $this->getLatestState()->shouldNotBe($state); 98 | } 99 | 100 | 101 | function it_starts_a_new_workflow_state( 102 | Transition $transition, 103 | Workflow $workflow, 104 | Step $step, 105 | Context $context, 106 | ErrorCollection $errorCollection, 107 | Properties $properties 108 | ) { 109 | $workflow->getName()->willReturn('workflow'); 110 | $step->getName()->willReturn('step'); 111 | $step->getWorkflowName()->willReturn('workflow'); 112 | 113 | $transition->getWorkflow()->willReturn($workflow); 114 | $transition->getName()->willReturn('transition_name'); 115 | $transition->getStepTo()->willReturn($step); 116 | 117 | $properties->toArray()->willReturn([]); 118 | $context->getProperties()->willReturn($properties); 119 | 120 | $context->getErrorCollection()->willReturn($errorCollection); 121 | $errorCollection->toArray()->willReturn([]); 122 | 123 | $this->beConstructedThrough('initialize', [$this->entityId, static::$entity]); 124 | $this->start($transition, $context, true)->shouldHaveType('Netzmacht\Workflow\Flow\State'); 125 | } 126 | 127 | public function it_gets_last_successful_state(State $state, State $failedState): void 128 | { 129 | $failedState->isSuccessful()->willReturn(false); 130 | $failedState->getStepName()->willReturn('failed'); 131 | 132 | $state->isSuccessful()->willReturn(true); 133 | $state->getStepName()->willReturn('start'); 134 | $state->getWorkflowName()->shouldBeCalled(); 135 | 136 | $this->beConstructedThrough('reconstitute', [$this->entityId, static::$entity, [$state, $failedState]]); 137 | 138 | $this->getCurrentStepName()->shouldReturn('start'); 139 | $this->getLatestState()->shouldReturn($state); 140 | $this->getLatestSuccessfulState()->shouldReturn($state); 141 | $this->getLatestStateOccurred()->shouldReturn($failedState); 142 | } 143 | 144 | public function it_gets_latest_state_from_history(State $state, State $failedState): void 145 | { 146 | $failedState->isSuccessful()->willReturn(false); 147 | $failedState->getStepName()->willReturn('failed'); 148 | 149 | $state->isSuccessful()->willReturn(true); 150 | $state->getStepName()->willReturn('start'); 151 | $state->getWorkflowName()->shouldBeCalled(); 152 | 153 | $this->beConstructedThrough('reconstitute', [$this->entityId, static::$entity, [$state, $failedState]]); 154 | 155 | $this->getLatestState(false)->shouldReturn($failedState); 156 | $this->getLatestSuccessfulState()->shouldReturn($state); 157 | $this->getLatestStateOccurred()->shouldReturn($failedState); 158 | } 159 | 160 | public function it_records_state_changes_and_release_them( 161 | Transition $transition, 162 | Workflow $workflow, 163 | Step $stepTo, 164 | Context $context, 165 | ErrorCollection $errorCollection, 166 | Properties $payload, 167 | Properties $properties 168 | ) : void { 169 | $transition->getWorkflow() 170 | ->willReturn($workflow); 171 | 172 | $transition->getName() 173 | ->willReturn('transition'); 174 | 175 | $transition->getStepTo() 176 | ->willReturn($stepTo); 177 | 178 | $workflow->getName() 179 | ->willReturn('workflow'); 180 | 181 | $stepTo->getName() 182 | ->willReturn('step_to'); 183 | 184 | $stepTo->getWorkflowName() 185 | ->willReturn('workflow'); 186 | 187 | $context->getErrorCollection() 188 | ->willReturn($errorCollection); 189 | 190 | $properties->toArray() 191 | ->willReturn([]); 192 | 193 | $payload->toArray() 194 | ->willReturn([]); 195 | 196 | $context->getProperties() 197 | ->willReturn($properties); 198 | 199 | $context->getPayload() 200 | ->willReturn($payload); 201 | 202 | $errorCollection->toArray() 203 | ->willReturn([]); 204 | 205 | $errorCollection->getErrors() 206 | ->willReturn([]); 207 | 208 | $this->start($transition, $context, true); 209 | $this->transit($transition, $context, true); 210 | 211 | $this->releaseRecordedStateChanges()->shouldHaveCount(2); 212 | $this->releaseRecordedStateChanges()->shouldHaveCount(0); 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /spec/Flow/Security/PermissionSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow\Security; 14 | 15 | use Netzmacht\Workflow\Flow\Security\Permission; 16 | use Netzmacht\Workflow\Flow\Workflow; 17 | use PhpSpec\ObjectBehavior; 18 | 19 | /** 20 | * Class PermissionSpec 21 | * 22 | * @package spec\Netzmacht\Workflow\Security 23 | */ 24 | class PermissionSpec extends ObjectBehavior 25 | { 26 | function let(Workflow $workflow) 27 | { 28 | $workflow->getName()->willReturn('workflow'); 29 | $this->beConstructedThrough('forWorkflow', [$workflow, 'perm']); 30 | } 31 | 32 | function it_is_initializable() 33 | { 34 | $this->shouldHaveType(Permission::class); 35 | } 36 | 37 | function it_has_a_workflow_name() 38 | { 39 | $this->getWorkflowName()->shouldReturn('workflow'); 40 | } 41 | 42 | function it_has_a_permission_id() 43 | { 44 | $this->getPermissionId()->shouldReturn('perm'); 45 | } 46 | 47 | function it_equals_if_workflow_and_permission_id_matches(Permission $permission) 48 | { 49 | $permission->__toString()->willReturn('workflow:perm'); 50 | 51 | $this->equals($permission)->shouldReturn(true); 52 | } 53 | 54 | function it_does_not_equals_if_not_the_same_workflow(Permission $permission) 55 | { 56 | $permission->__toString()->willReturn('workflow2:perm'); 57 | 58 | $this->equals($permission)->shouldReturn(false); 59 | } 60 | 61 | function it_casts_to_string() 62 | { 63 | $this->__toString()->shouldReturn('workflow:perm'); 64 | } 65 | 66 | function it_reconstitutes_from_string() 67 | { 68 | $this->beConstructedThrough('fromString', ['workflow:perm']); 69 | 70 | $this->getWorkflowName()->shouldReturn('workflow'); 71 | $this->getPermissionId()->shouldReturn('perm'); 72 | } 73 | 74 | function it_reconstitutes_for_workflow_name() 75 | { 76 | $this->beConstructedThrough('forWorkflowName', ['workflow', 'perm']); 77 | 78 | $this->getWorkflowName()->shouldReturn('workflow'); 79 | $this->getPermissionId()->shouldReturn('perm'); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spec/Flow/StepSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow; 14 | 15 | use Netzmacht\Workflow\Flow\Security\Permission; 16 | use PhpSpec\ObjectBehavior; 17 | 18 | /** 19 | * Class StepSpec 20 | * 21 | * @package spec\Netzmacht\Workflow\Flow 22 | */ 23 | class StepSpec extends ObjectBehavior 24 | { 25 | const NAME = 'test'; 26 | const LABEL = 'label'; 27 | const WORKFLOW_NAME = 'workflow'; 28 | 29 | function let() 30 | { 31 | $this->beConstructedWith(self::NAME, self::LABEL, [], self::WORKFLOW_NAME); 32 | } 33 | 34 | function it_is_initializable() 35 | { 36 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Step'); 37 | } 38 | 39 | function it_behaves_like_base_object() 40 | { 41 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Base'); 42 | } 43 | 44 | function it_is_not_final_by_default() 45 | { 46 | $this->isFinal()->shouldReturn(false); 47 | } 48 | 49 | function it_can_be_final() 50 | { 51 | $this->setFinal(true)->shouldReturn($this); 52 | $this->isFinal()->shouldReturn(true); 53 | } 54 | 55 | function it_has_no_allowed_transitions_by_default() 56 | { 57 | $this->getAllowedTransitions()->shouldBeEqualTo([]); 58 | } 59 | 60 | function it_allows_transition() 61 | { 62 | $this->isTransitionAllowed('test')->shouldReturn(false); 63 | $this->allowTransition('test')->shouldReturn($this); 64 | $this->isTransitionAllowed('test')->shouldReturn(true); 65 | } 66 | 67 | function it_disallows_transition() 68 | { 69 | $this->allowTransition('test'); 70 | $this->isTransitionAllowed('test')->shouldReturn(true); 71 | $this->disallowTransition('test')->shouldReturn($this); 72 | $this->isTransitionAllowed('test')->shouldReturn(false); 73 | } 74 | 75 | function it_returns_allowed_transitions() 76 | { 77 | $this->allowTransition('test')->shouldReturn($this); 78 | $this->allowTransition('bar')->shouldReturn($this); 79 | 80 | $this->getAllowedTransitions()->shouldReturn(['test', 'bar']); 81 | } 82 | 83 | function it_does_not_allow_transition_when_being_final() 84 | { 85 | $this->allowTransition('test')->shouldReturn($this); 86 | $this->isTransitionAllowed('test')->shouldReturn(true); 87 | 88 | $this->setFinal(true); 89 | $this->isTransitionAllowed('test')->shouldReturn(false); 90 | } 91 | 92 | function it_have_a_permission(Permission $permission) 93 | { 94 | $permission->equals($permission)->willReturn(false); 95 | 96 | $this->getPermission()->shouldReturn(null); 97 | $this->hasPermission($permission)->shouldReturn(false); 98 | 99 | $permission->equals($permission)->willReturn(true); 100 | 101 | $this->setPermission($permission)->shouldReturn($this); 102 | $this->hasPermission($permission)->shouldReturn(true); 103 | $this->getPermission()->shouldReturn($permission); 104 | } 105 | 106 | function it_might_know_the_workflow_name(): void 107 | { 108 | $this->getWorkflowName()->shouldBe(self::WORKFLOW_NAME); 109 | } 110 | 111 | function it_migt_have_not_the_workflow_name(): void 112 | { 113 | $this->beConstructedWith(self::NAME, self::LABEL); 114 | 115 | $this->getWorkflowName()->shouldBeNull(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /spec/Flow/WorkflowSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Flow; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Flow\Condition\Workflow\Condition; 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Step; 20 | use Netzmacht\Workflow\Flow\Transition; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * Class WorkflowSpec 25 | * 26 | * @package spec\Netzmacht\Workflow\Flow 27 | */ 28 | class WorkflowSpec extends ObjectBehavior 29 | { 30 | const NAME = 'workflow'; 31 | const PROVIDER = 'provider_name'; 32 | const START_STEP = 'start_step'; 33 | 34 | protected static $entity = ['id' => 5]; 35 | 36 | function let(Step $transitionStep, Transition $transition) 37 | { 38 | $transitionStep->getName()->willReturn(static::START_STEP); 39 | 40 | $transition->getName()->willReturn('start'); 41 | 42 | $this->beConstructedWith(static::NAME, static::PROVIDER); 43 | 44 | $this->addStep($transitionStep); 45 | $this->addTransition($transition, true); 46 | } 47 | 48 | function it_is_initializable() 49 | { 50 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Workflow'); 51 | } 52 | 53 | function it_behaves_like_base() 54 | { 55 | $this->shouldHaveType('Netzmacht\Workflow\Flow\Base'); 56 | } 57 | 58 | function it_adds_a_step(Step $anotherStep) 59 | { 60 | $anotherStep->getName()->willReturn('another'); 61 | 62 | $this->addStep($anotherStep)->shouldReturn($this); 63 | $this->getStep('another')->shouldReturn($anotherStep); 64 | } 65 | 66 | function it_throws_if_step_not_exists() 67 | { 68 | $this->shouldThrow('Netzmacht\Workflow\Flow\Exception\StepNotFoundException')->duringGetStep('not_set'); 69 | } 70 | 71 | function it_adds_a_transition(Transition $anotherTransition) 72 | { 73 | $anotherTransition->getName()->willReturn('another'); 74 | 75 | $this->addTransition($anotherTransition)->shouldReturn($this); 76 | $this->getTransition('another')->shouldReturn($anotherTransition); 77 | } 78 | 79 | function it_throws_if_transition_not_exists() 80 | { 81 | $this 82 | ->shouldThrow('Netzmacht\Workflow\Flow\Exception\TransitionNotFound') 83 | ->duringGetTransition('not_set'); 84 | } 85 | 86 | function it_has_a_start_transition(Transition $transition) 87 | { 88 | $this->setStartTransition('start')->shouldReturn($this); 89 | $this->getStartTransition()->shouldReturn($transition); 90 | } 91 | 92 | function it_throws_if_start_transition_is_not_part_of_workflow() 93 | { 94 | $this 95 | ->shouldThrow('Netzmacht\Workflow\Flow\Exception\TransitionNotFound') 96 | ->duringSetStartTransition('not_set'); 97 | } 98 | 99 | function it_knows_if_transition_exists() 100 | { 101 | $this->hasTransition('start')->shouldReturn(true); 102 | $this->hasTransition('test')->shouldReturn(false); 103 | } 104 | 105 | function it_gets_all_transitions(Transition $transition) 106 | { 107 | $this->getTransitions()->shouldReturn([$transition]); 108 | } 109 | 110 | function it_knows_if_start_transition_is_available_for_an_item( 111 | Item $item, 112 | Context $context 113 | ) { 114 | $item->isWorkflowStarted()->willReturn(false); 115 | 116 | $this->isTransitionAvailable($item, $context, 'start')->shouldReturn(true); 117 | } 118 | 119 | function it_knows_if_start_transition_is_not_available_for_an_item(Item $item, Context $context) 120 | { 121 | $item->isWorkflowStarted()->willReturn(false); 122 | 123 | $this->isTransitionAvailable($item, $context, 'start2')->shouldReturn(false); 124 | } 125 | 126 | function it_knows_if_transition_is_not_available_for_an_item( 127 | Item $item, 128 | Step $step, 129 | Context $context 130 | ) { 131 | $item->isWorkflowStarted()->willReturn(true); 132 | $item->getCurrentStepName()->willReturn('started'); 133 | 134 | $step->getName()->willReturn('started'); 135 | $step->isTransitionAllowed('start')->willReturn(false); 136 | $this->addStep($step); 137 | 138 | $this->isTransitionAvailable($item, $context, 'start')->shouldReturn(false); 139 | } 140 | 141 | function it_knows_if_transition_is_available_for_an_item( 142 | Item $item, 143 | Step $step, 144 | Context $context, 145 | Transition $transition 146 | ) { 147 | $item->isWorkflowStarted()->willReturn(true); 148 | $item->getCurrentStepName()->willReturn('started'); 149 | 150 | $transition->getName()->willReturn('next'); 151 | $transition->isAvailable($item, $context)->shouldBeCalled()->willReturn(true); 152 | $this->addTransition($transition); 153 | 154 | $step->getName()->willReturn('started'); 155 | $step->isTransitionAllowed('next')->willReturn(true); 156 | $this->addStep($step); 157 | 158 | $this->isTransitionAvailable($item, $context, 'next')->shouldReturn(true); 159 | } 160 | 161 | function it_can_be_limited_by_conditions(Condition $condition) 162 | { 163 | $this->getCondition()->shouldBe(null); 164 | 165 | $this->addCondition($condition)->shouldReturn($this); 166 | 167 | $this->getCondition()->shouldHaveType('Netzmacht\Workflow\Flow\Condition\Workflow\AndCondition'); 168 | $this->getCondition()->getConditions()->shouldReturn([$condition]); 169 | } 170 | 171 | function it_is_limited_to_an_provider_name() 172 | { 173 | $this->getProviderName()->shouldReturn(static::PROVIDER); 174 | } 175 | 176 | function it_matches_if_no_condition_is_set() 177 | { 178 | $entityId = EntityId::fromProviderNameAndId('entity', 2); 179 | 180 | $this->supports($entityId, static::$entity)->shouldReturn(true); 181 | } 182 | 183 | function it_matches_if_condition_does(Condition $condition) 184 | { 185 | $entityId = EntityId::fromProviderNameAndId('entity', 2); 186 | $condition->match($this, $entityId, static::$entity)->willReturn(true); 187 | 188 | $this->addCondition($condition); 189 | $this->supports($entityId, static::$entity)->shouldReturn(true); 190 | } 191 | 192 | function it_does_not_match_if_condition_does_not(Condition $condition) 193 | { 194 | $entityId = EntityId::fromProviderNameAndId('entity', 2); 195 | 196 | $condition->match($this, $entityId, static::$entity)->willReturn(false); 197 | 198 | $this->addCondition($condition); 199 | $this->supports($entityId, static::$entity)->shouldReturn(false); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /spec/Handler/RepositoryBasedTransitionHandlerFactorySpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Handler; 14 | 15 | use Netzmacht\Workflow\Data\EntityManager; 16 | use Netzmacht\Workflow\Data\EntityRepository; 17 | use Netzmacht\Workflow\Data\StateRepository; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Workflow; 20 | use Netzmacht\Workflow\Transaction\TransactionHandler; 21 | use PhpSpec\ObjectBehavior; 22 | 23 | /** 24 | * Class EventDispatchingTransitionHandlerFactorySpec 25 | * 26 | * @package spec\Netzmacht\Workflow\Factory 27 | */ 28 | class RepositoryBasedTransitionHandlerFactorySpec extends ObjectBehavior 29 | { 30 | protected static $entity = ['id' => 5]; 31 | 32 | function let(TransactionHandler $transactionHandler, EntityManager $entityManager) 33 | { 34 | $this->beConstructedWith($entityManager, $transactionHandler); 35 | } 36 | 37 | function it_is_initializable() 38 | { 39 | $this->shouldHaveType('Netzmacht\Workflow\Handler\RepositoryBasedTransitionHandlerFactory'); 40 | } 41 | 42 | function it_gets_entity_manager(EntityManager $entityManager) 43 | { 44 | $this->getEntityManager()->shouldReturn($entityManager); 45 | } 46 | 47 | function it_gets_transaction_handler(TransactionHandler $transactionHandler) 48 | { 49 | $this->getTransactionHandler()->shouldReturn($transactionHandler); 50 | } 51 | 52 | function it_creates_the_repository_based_transition_handler( 53 | Item $item, 54 | Workflow $workflow, 55 | StateRepository $stateRepository, 56 | EntityManager $entityManager, 57 | EntityRepository $entityRepository 58 | ) { 59 | $entityManager->getRepository('test')->willReturn($entityRepository); 60 | 61 | $item->isWorkflowStarted()->willReturn(false); 62 | $item->getEntity()->willReturn(static::$entity); 63 | 64 | $this->createTransitionHandler( 65 | $item, 66 | $workflow, 67 | null, 68 | 'test', 69 | $stateRepository 70 | ) 71 | ->shouldHaveType('Netzmacht\Workflow\Handler\RepositoryBasedTransitionHandler'); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /spec/Handler/RepositoryBasedTransitionHandlerSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Handler; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Data\EntityRepository; 17 | use Netzmacht\Workflow\Data\StateRepository; 18 | use Netzmacht\Workflow\Flow\Context; 19 | use Netzmacht\Workflow\Flow\Item; 20 | use Netzmacht\Workflow\Flow\State; 21 | use Netzmacht\Workflow\Flow\Step; 22 | use Netzmacht\Workflow\Flow\Transition; 23 | use Netzmacht\Workflow\Flow\Workflow; 24 | use Netzmacht\Workflow\Transaction\TransactionHandler; 25 | use PhpSpec\ObjectBehavior; 26 | use Prophecy\Argument; 27 | 28 | /** 29 | * Class RepositoryBasedTransitionHandlerSpec 30 | * 31 | * @package spec\Netzmacht\Workflow\Handler 32 | */ 33 | class RepositoryBasedTransitionHandlerSpec extends ObjectBehavior 34 | { 35 | const TRANSITION_NAME = 'transition_name'; 36 | 37 | const STEP_NAME = 'step_name'; 38 | const WORKFLOW_NAME = 'workflow_name'; 39 | 40 | protected static $entity = ['id' => 5]; 41 | 42 | /** 43 | * @var EntityId 44 | */ 45 | private $entityId; 46 | 47 | function let( 48 | Item $item, 49 | Workflow $workflow, 50 | EntityRepository $entityRepository, 51 | StateRepository $stateRepository, 52 | TransactionHandler $transactionHandler, 53 | Step $step, 54 | Transition $transition, 55 | State $state 56 | ) { 57 | $this->entityId = EntityId::fromProviderNameAndId('entity', '2'); 58 | 59 | $workflow->getStep(static::STEP_NAME)->willReturn($step); 60 | $workflow->getStartTransition()->willReturn($transition); 61 | $workflow->getName()->willReturn(static::WORKFLOW_NAME); 62 | 63 | $step->isTransitionAllowed(static::TRANSITION_NAME)->willReturn(true); 64 | $workflow->getTransition(static::TRANSITION_NAME)->willReturn($transition); 65 | 66 | $transition->getName()->willReturn(static::TRANSITION_NAME); 67 | $transition->getRequiredPayloadProperties($item)->willReturn([]); 68 | 69 | $item->transit($transition, Argument::type(Context::class)) 70 | ->willReturn($state); 71 | 72 | $item->isWorkflowStarted()->willReturn(true); 73 | $item->getCurrentStepName()->willReturn(static::STEP_NAME); 74 | $item->getEntity()->willReturn(static::$entity); 75 | 76 | $this->beConstructedWith( 77 | $item, 78 | $workflow, 79 | static::TRANSITION_NAME, 80 | $entityRepository, 81 | $stateRepository, 82 | $transactionHandler 83 | ); 84 | } 85 | 86 | function it_is_initializable() 87 | { 88 | $this->shouldHaveType('Netzmacht\Workflow\Handler\RepositoryBasedTransitionHandler'); 89 | } 90 | 91 | function it_gets_workflow(Workflow $workflow) 92 | { 93 | $this->getWorkflow()->shouldReturn($workflow); 94 | } 95 | 96 | function it_gets_start_transition_if_not_started( 97 | Item $item, 98 | Workflow $workflow, 99 | EntityRepository $entityRepository, 100 | StateRepository $stateRepository, 101 | TransactionHandler $transactionHandler, 102 | Transition $transition 103 | ) { 104 | $this->beConstructedWith( 105 | $item, 106 | $workflow, 107 | null, 108 | $entityRepository, 109 | $stateRepository, 110 | $transactionHandler 111 | ); 112 | 113 | $item->isWorkflowStarted()->willReturn(false); 114 | $item->getEntityId()->willReturn($this->entityId); 115 | 116 | $workflow->getStartTransition()->willReturn($transition); 117 | 118 | $this->getTransition()->shouldReturn($transition); 119 | } 120 | 121 | function it_gets_transition_if_already_started(Item $item, Workflow $workflow, Transition $transition) 122 | { 123 | $item->isWorkflowStarted()->willReturn(true); 124 | 125 | $workflow->getTransition(static::TRANSITION_NAME)->willReturn($transition); 126 | 127 | $this->getTransition()->shouldReturn($transition); 128 | } 129 | 130 | function it_gets_item(Item $item) 131 | { 132 | $this->getItem()->shouldReturn($item); 133 | } 134 | 135 | function it_gets_current_step_for_started_workflow(Item $item, Workflow $workflow, Step $step) 136 | { 137 | $item->isWorkflowStarted()->willReturn(true); 138 | $item->getCurrentStepName()->willReturn('start'); 139 | 140 | $workflow->getStep('start')->willReturn($step); 141 | 142 | $this->getCurrentStep()->shouldReturn($step); 143 | } 144 | 145 | function it_gets_null_instead_of_step_if_not_started( 146 | Item $item, 147 | Workflow $workflow, 148 | EntityRepository $entityRepository, 149 | StateRepository $stateRepository, 150 | TransactionHandler $transactionHandler 151 | ) { 152 | $this->beConstructedWith( 153 | $item, 154 | $workflow, 155 | null, 156 | $entityRepository, 157 | $stateRepository, 158 | $transactionHandler 159 | ); 160 | 161 | $item->isWorkflowStarted()->willReturn(false); 162 | 163 | $this->getCurrentStep()->shouldBeNull(); 164 | } 165 | 166 | function it_checks_if_workflow_is_started(Item $item) 167 | { 168 | $item->isWorkflowStarted()->willReturn(true); 169 | $this->isWorkflowStarted()->shouldReturn(true); 170 | } 171 | 172 | function it_checks_if_workflow_is_not_started( 173 | Item $item, 174 | Workflow $workflow, 175 | EntityRepository $entityRepository, 176 | StateRepository $stateRepository, 177 | TransactionHandler $transactionHandler 178 | ) { 179 | $this->beConstructedWith( 180 | $item, 181 | $workflow, 182 | null, 183 | $entityRepository, 184 | $stateRepository, 185 | $transactionHandler 186 | ); 187 | 188 | $item->isWorkflowStarted()->willReturn(false); 189 | $this->isWorkflowStarted()->shouldReturn(false); 190 | } 191 | 192 | function it_checks_if_input_data_is_required(Workflow $workflow, Transition $transition, Item $item) 193 | { 194 | $workflow->getStartTransition()->willReturn($transition); 195 | $transition->getRequiredPayloadProperties($item)->willReturn(['foo']); 196 | 197 | $this->getRequiredPayloadProperties()->shouldReturn(['foo']); 198 | } 199 | 200 | function it_checks_if_input_data_is_not_required(Workflow $workflow, Transition $transition, Item $item) 201 | { 202 | $workflow->getStartTransition()->willReturn($transition); 203 | $transition->getRequiredPayloadProperties($item)->willReturn([]); 204 | 205 | $this->getRequiredPayloadProperties()->shouldReturn([]); 206 | } 207 | 208 | function it_gets_the_context() 209 | { 210 | $this->getContext()->shouldHaveType(Context::class); 211 | } 212 | 213 | function it_validates(Workflow $workflow, Transition $transition, Item $item) 214 | { 215 | $workflow->getStartTransition()->willReturn($transition); 216 | $transition->getName()->willReturn(static::TRANSITION_NAME); 217 | 218 | $transition->getRequiredPayloadProperties($item)->willReturn(['foo']); 219 | 220 | $transition->validate($item, Argument::type(Context::class)) 221 | ->willReturn(true) 222 | ->shouldBeCalled(); 223 | 224 | $transition->checkPreCondition($item, Argument::type(Context::class)) 225 | ->shouldBeCalled() 226 | ->willReturn(true); 227 | 228 | $transition->checkCondition($item, Argument::type(Context::class)) 229 | ->shouldBeCalled() 230 | ->willReturn(true); 231 | 232 | $this->validate([])->shouldReturn(true); 233 | } 234 | 235 | function it_throws_during_transits_if_not_validated(Workflow $workflow, Transition $transition) 236 | { 237 | $workflow->getStartTransition()->willReturn($transition); 238 | 239 | $this->shouldThrow('Netzmacht\Workflow\Exception\WorkflowException')->duringTransit(); 240 | } 241 | 242 | function it_transits_to_next_state(Transition $transition, Item $item, State $state) 243 | { 244 | $item->releaseRecordedStateChanges() 245 | ->shouldBeCalledOnce() 246 | ->willReturn([$state]); 247 | 248 | $transition->validate($item, Argument::type(Context::class)) 249 | ->willReturn(true) 250 | ->shouldBeCalled(); 251 | 252 | $transition->execute($item, Argument::type(Context::class)) 253 | ->willReturn($state) 254 | ->shouldBeCalledOnce(); 255 | 256 | $transition->checkCondition($item, Argument::type(Context::class)) 257 | ->willReturn(true) 258 | ->shouldBeCalled(); 259 | 260 | $transition->checkPreCondition($item, Argument::type(Context::class)) 261 | ->willReturn(true) 262 | ->shouldBeCalled(); 263 | 264 | $this->validate([]); 265 | $this->transit()->shouldHaveType(State::class); 266 | } 267 | 268 | function it_checks_if_transition_is_available(Transition $transition, Item $item) 269 | { 270 | $transition->getName()->willReturn(static::TRANSITION_NAME); 271 | $transition->isAvailable( 272 | $item, 273 | Argument::type(Context::class) 274 | )->willReturn(true); 275 | 276 | $this->isAvailable()->shouldReturn(true); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /spec/Manager/WorkflowManagerSpec.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | namespace spec\Netzmacht\Workflow\Manager; 14 | 15 | use Netzmacht\Workflow\Data\EntityId; 16 | use Netzmacht\Workflow\Data\StateRepository; 17 | use Netzmacht\Workflow\Exception\WorkflowNotFound; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\State; 20 | use Netzmacht\Workflow\Flow\Step; 21 | use Netzmacht\Workflow\Flow\Transition; 22 | use Netzmacht\Workflow\Flow\Workflow; 23 | use Netzmacht\Workflow\Handler\TransitionHandler; 24 | use Netzmacht\Workflow\Handler\TransitionHandlerFactory; 25 | use PhpSpec\ObjectBehavior; 26 | use Prophecy\Argument; 27 | 28 | /** 29 | * Class ManagerSpec 30 | * 31 | * @package spec\Netzmacht\Contao\Workflow 32 | */ 33 | class WorkflowManagerSpec extends ObjectBehavior 34 | { 35 | const ENTITY_PROVIDER_NAME = 'provider_name'; 36 | 37 | const ENTITY_ID = 5; 38 | 39 | protected static $entity = ['id' => 5]; 40 | 41 | function it_is_initializable() 42 | { 43 | $this->shouldHaveType('Netzmacht\Workflow\Manager\Manager'); 44 | } 45 | 46 | function let( 47 | TransitionHandlerFactory $transitionHandlerFactory, 48 | StateRepository $stateRepository, 49 | Workflow $workflow 50 | ) { 51 | $workflow->getName()->willReturn('workflow_a'); 52 | 53 | $this->beConstructedWith($transitionHandlerFactory, $stateRepository, [$workflow]); 54 | } 55 | 56 | function it_gets_workflow(Workflow $workflow) 57 | { 58 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 59 | 60 | $workflow->supports($entityId, static::$entity)->willReturn(true); 61 | 62 | $this->getWorkflow($entityId, static::$entity)->shouldReturn($workflow); 63 | } 64 | 65 | function it_gets_workflow_by_item(Workflow $workflow, Item $item) 66 | { 67 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 68 | 69 | $item->getWorkflowName()->willReturn('workflow_a'); 70 | $item->getEntityId()->willReturn($entityId); 71 | $item->getEntity()->willReturn(static::$entity); 72 | 73 | $workflow->supports($entityId, static::$entity)->willReturn(true); 74 | 75 | $this->getWorkflowByItem($item)->shouldReturn($workflow); 76 | } 77 | 78 | function it_adds_workflow(Workflow $anotherWorkflow) 79 | { 80 | $anotherWorkflow->getName()->willReturn('another'); 81 | 82 | $this->addWorkflow($anotherWorkflow)->shouldReturn($this); 83 | $this->getWorkflowByName('another')->shouldReturn($anotherWorkflow); 84 | } 85 | 86 | function it_returns_false_if_no_supported_workflow_found(Workflow $workflow) 87 | { 88 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 89 | 90 | $workflow->supports($entityId, static::$entity)->willReturn(false); 91 | } 92 | 93 | function it_throws_workflow_not_found_when_specific_workflow_not_exists() 94 | { 95 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 96 | 97 | $this->shouldThrow(WorkflowNotFound::class) 98 | ->during('getWorkflowByName', [$entityId, static::$entity]); 99 | } 100 | 101 | function it_knows_if_matching_workflow_exists(Workflow $workflow) 102 | { 103 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 104 | 105 | $workflow->supports($entityId, static::$entity)->willReturn(true); 106 | $this->hasWorkflow($entityId, static::$entity)->shouldReturn(true); 107 | } 108 | 109 | function it_knows_if_no_matching_workflow_exists(Workflow $workflow) 110 | { 111 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 112 | 113 | $workflow->supports($entityId, static::$entity)->willReturn(false); 114 | $this->hasWorkflow($entityId, static::$entity)->shouldReturn(false); 115 | } 116 | 117 | function it_adds_an_workflow(Workflow $anotherWorkflow) 118 | { 119 | $this->getWorkflows()->shouldNotContain($anotherWorkflow); 120 | $this->addWorkflow($anotherWorkflow)->shouldReturn($this); 121 | $this->getWorkflows()->shouldContain($anotherWorkflow); 122 | } 123 | 124 | function it_returns_false_if_no_matching_workflow_found( 125 | Workflow $workflow, 126 | Item $item 127 | ) { 128 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 129 | 130 | $item->getEntityId()->willReturn($entityId); 131 | $item->getEntity()->willReturn(static::$entity); 132 | 133 | $workflow->supports($entityId, static::$entity)->willReturn(false); 134 | $this->handle($item)->shouldReturn(null); 135 | } 136 | 137 | function it_creates_handler_for_start_transition( 138 | Workflow $workflow, 139 | Item $item, 140 | TransitionHandlerFactory $transitionHandlerFactory, 141 | StateRepository $stateRepository, 142 | TransitionHandler $transitionHandler 143 | ) { 144 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 145 | 146 | $item->getWorkflowName()->willReturn('workflow_a'); 147 | $item->getEntityId()->willReturn($entityId); 148 | $item->getEntity()->willReturn(static::$entity); 149 | $item->isWorkflowStarted()->willReturn(false); 150 | 151 | $workflow->supports($entityId, static::$entity)->willReturn(true); 152 | 153 | $transitionHandlerFactory->createTransitionHandler( 154 | $item, 155 | $workflow, 156 | Argument::any(), 157 | static::ENTITY_PROVIDER_NAME, 158 | $stateRepository 159 | )->willReturn($transitionHandler); 160 | 161 | $this->handle($item)->shouldReturn($transitionHandler); 162 | } 163 | 164 | function it_creates_handler_for_ongoing_transition( 165 | Workflow $workflow, 166 | Item $item, 167 | TransitionHandlerFactory $transitionHandlerFactory, 168 | StateRepository $stateRepository, 169 | TransitionHandler $transitionHandler, 170 | Transition $transition, 171 | Step $step 172 | ) { 173 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 174 | 175 | $step->getName()->willReturn('start'); 176 | $step->isTransitionAllowed('next')->willReturn(true); 177 | 178 | $item->getEntityId()->willReturn($entityId); 179 | $item->getEntity()->willReturn(static::$entity); 180 | $item->isWorkflowStarted()->willReturn(true); 181 | $item->getCurrentStepName()->willReturn('start'); 182 | $item->getWorkflowName()->willReturn('workflow_a'); 183 | 184 | $workflow->supports($entityId, static::$entity)->willReturn(true); 185 | $workflow->getStep('start')->willReturn($step); 186 | $workflow->getTransition('next')->willReturn($transition); 187 | $workflow->getName()->willReturn('workflow_a'); 188 | 189 | $transitionHandlerFactory->createTransitionHandler( 190 | $item, 191 | $workflow, 192 | Argument::any(), 193 | static::ENTITY_PROVIDER_NAME, 194 | $stateRepository 195 | ) 196 | ->willReturn($transitionHandler); 197 | 198 | $this->handle($item, 'next')->shouldReturn($transitionHandler); 199 | } 200 | 201 | 202 | function it_throws_than_matches_workflow_is_not_same_as_current( 203 | Workflow $workflow, 204 | Item $item, 205 | Transition $transition, 206 | Step $step 207 | ) { 208 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 209 | 210 | $step->getName()->willReturn('start'); 211 | $step->isTransitionAllowed('next')->willReturn(true); 212 | 213 | $item->getEntityId()->willReturn($entityId); 214 | $item->getEntity()->willReturn(static::$entity); 215 | $item->isWorkflowStarted()->willReturn(true); 216 | $item->getCurrentStepName()->willReturn('start'); 217 | $item->getWorkflowName()->willReturn('workflow_a'); 218 | 219 | $workflow->supports($entityId, static::$entity)->willReturn(true); 220 | $workflow->getStep('start')->willReturn($step); 221 | $workflow->getTransition('next')->willReturn($transition); 222 | $workflow->getName()->willReturn('workflow_b'); 223 | 224 | $this 225 | ->shouldThrow('Netzmacht\Workflow\Exception\WorkflowException') 226 | ->duringHandle($item, 'next'); 227 | } 228 | 229 | function it_creates_an_item( 230 | StateRepository $stateRepository, 231 | State $state 232 | ) { 233 | $entityId = EntityId::fromProviderNameAndId(static::ENTITY_PROVIDER_NAME, static::ENTITY_ID); 234 | 235 | $state->getStepName()->willReturn('step'); 236 | $state->getWorkflowName()->willReturn('workflow'); 237 | $state->isSuccessful()->willReturn(true); 238 | 239 | $stateRepository->find($entityId)->willReturn([$state]); 240 | 241 | $this->createItem($entityId, static::$entity)->shouldHaveType('Netzmacht\Workflow\Flow\Item'); 242 | } 243 | 244 | } 245 | -------------------------------------------------------------------------------- /spec/Util/ComparisonSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType('Netzmacht\Workflow\Util\Comparison'); 18 | } 19 | 20 | function it_checks_if_values_equals() 21 | { 22 | $this->equals(1, 2)->shouldReturn(false); 23 | $this->equals(1, 1)->shouldReturn(true); 24 | } 25 | 26 | function it_checks_if_values_not_equals() 27 | { 28 | $this->notEquals(1, 2)->shouldReturn(true); 29 | $this->notEquals(1, 1)->shouldReturn(false); 30 | } 31 | 32 | function it_checks_if_values_are_identical() 33 | { 34 | $this->identical(1, '1')->shouldReturn(false); 35 | $this->identical(1, 1)->shouldReturn(true); 36 | } 37 | 38 | function it_checks_if_values_are_not_identical() 39 | { 40 | $this->notIdentical(1, '1')->shouldReturn(true); 41 | $this->notIdentical(1, 1)->shouldReturn(false); 42 | } 43 | 44 | function it_checks_if_value_is_greater_than_other() 45 | { 46 | $this->greaterThan(1, 2)->shouldReturn(false); 47 | $this->greaterThan(1, 1)->shouldReturn(false); 48 | $this->greaterThan(2, 1)->shouldReturn(true); 49 | } 50 | 51 | function it_checks_if_value_is_greater_than_or_equals_other() 52 | { 53 | $this->greaterThanOrEquals(1, 2)->shouldReturn(false); 54 | $this->greaterThanOrEquals(1, 1)->shouldReturn(true); 55 | $this->greaterThanOrEquals(2, 1)->shouldReturn(true); 56 | } 57 | 58 | function it_checks_if_value_is_lesser_than_other() 59 | { 60 | $this->lesserThan(2, 1)->shouldReturn(false); 61 | $this->lesserThan(1, 1)->shouldReturn(false); 62 | $this->lesserThan(1, 2)->shouldReturn(true); 63 | } 64 | 65 | function it_checks_if_value_is_lesser_than_or_equals_other() 66 | { 67 | $this->lesserThanOrEquals(2, 1)->shouldReturn(false); 68 | $this->lesserThanOrEquals(1, 1)->shouldReturn(true); 69 | $this->lesserThanOrEquals(1, 2)->shouldReturn(true); 70 | } 71 | 72 | function it_compare_handles_equals() 73 | { 74 | $this->compare(1, 2, Comparison::EQUALS)->shouldReturn(false); 75 | $this->compare(2, 1, Comparison::EQUALS)->shouldReturn(false); 76 | $this->compare(1, 1, Comparison::EQUALS)->shouldReturn(true); 77 | } 78 | 79 | function it_compare_handles_not_equals() 80 | { 81 | $this->compare(1, 2, Comparison::NOT_EQUALS)->shouldReturn(true); 82 | $this->compare(2, 1, Comparison::NOT_EQUALS)->shouldReturn(true); 83 | $this->compare(1, 1, Comparison::NOT_EQUALS)->shouldReturn(false); 84 | } 85 | 86 | function it_compare_handles_identical() 87 | { 88 | $this->compare(1, '1', Comparison::IDENTICAL)->shouldReturn(false); 89 | $this->compare(1, 1, Comparison::IDENTICAL)->shouldReturn(true); 90 | } 91 | 92 | function it_compare_handles_not_identical() 93 | { 94 | $this->compare(1, '1', Comparison::NOT_IDENTICAL)->shouldReturn(true); 95 | $this->compare(1, 1, Comparison::NOT_IDENTICAL)->shouldReturn(false); 96 | } 97 | 98 | function it_compare_handles_greater_than() 99 | { 100 | $this->compare(1, 2, Comparison::GREATER_THAN)->shouldReturn(false); 101 | $this->compare(2, 2, Comparison::GREATER_THAN)->shouldReturn(false); 102 | $this->compare(2, 1, Comparison::GREATER_THAN)->shouldReturn(true); 103 | } 104 | 105 | function it_compare_handles_greater_than_or_equals() 106 | { 107 | $this->compare(1, 2, Comparison::GREATER_THAN_OR_EQUALS)->shouldReturn(false); 108 | $this->compare(2, 2, Comparison::GREATER_THAN_OR_EQUALS)->shouldReturn(true); 109 | $this->compare(2, 1, Comparison::GREATER_THAN_OR_EQUALS)->shouldReturn(true); 110 | } 111 | 112 | function it_compare_handles_lesser_than() 113 | { 114 | $this->compare(1, 2, Comparison::LESSER_THAN)->shouldReturn(true); 115 | $this->compare(2, 2, Comparison::LESSER_THAN)->shouldReturn(false); 116 | $this->compare(2, 1, Comparison::LESSER_THAN)->shouldReturn(false); 117 | } 118 | 119 | function it_compare_handles_lesser_than_or_equals() 120 | { 121 | $this->compare(2, 1, Comparison::LESSER_THAN_OR_EQUALS)->shouldReturn(false); 122 | $this->compare(2, 2, Comparison::LESSER_THAN_OR_EQUALS)->shouldReturn(true); 123 | $this->compare(1, 2, Comparison::LESSER_THAN_OR_EQUALS)->shouldReturn(true); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Data/EntityId.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Data; 16 | 17 | use Assert\Assertion; 18 | 19 | /** 20 | * Class EntityId identifies an entity by using its row id and provider name. 21 | * 22 | * @package Netzmacht\Workflow\Data 23 | */ 24 | final class EntityId 25 | { 26 | /** 27 | * The identifier. Usually a database id. 28 | * 29 | * @var mixed 30 | */ 31 | private $identifier; 32 | 33 | /** 34 | * The provider name. Usually the database table name. 35 | * 36 | * @var string 37 | */ 38 | private $providerName; 39 | 40 | /** 41 | * Construct. 42 | * 43 | * @param string $providerName The provider name. 44 | * @param mixed $identifier The identifier. 45 | */ 46 | private function __construct(string $providerName, $identifier) 47 | { 48 | // cast to int, but not for uuids 49 | if (is_numeric($identifier)) { 50 | $identifier = (int) $identifier; 51 | } 52 | 53 | $this->providerName = $providerName; 54 | $this->identifier = $identifier; 55 | } 56 | 57 | /** 58 | * Great the entity id from an string. 59 | * 60 | * @param string $entityId Entity id as string representation. For example provider::2. 61 | * 62 | * @return static 63 | */ 64 | public static function fromString(string $entityId): self 65 | { 66 | list($providerName, $identifier) = explode('::', $entityId, 2); 67 | 68 | Assertion::notEmpty($providerName); 69 | Assertion::notEmpty($identifier); 70 | 71 | return new static($providerName, $identifier); 72 | } 73 | 74 | /** 75 | * Create the entity id by provider name and identifier. 76 | * 77 | * @param string $providerName The provider name. 78 | * @param mixed $identifier The identifier. 79 | * 80 | * @return static 81 | */ 82 | public static function fromProviderNameAndId(string $providerName, $identifier): self 83 | { 84 | return new static($providerName, $identifier); 85 | } 86 | 87 | /** 88 | * Get the identifier. 89 | * 90 | * @return mixed 91 | */ 92 | public function getIdentifier() 93 | { 94 | return $this->identifier; 95 | } 96 | 97 | /** 98 | * Get the provider name. 99 | * 100 | * @return string 101 | */ 102 | public function getProviderName(): string 103 | { 104 | return $this->providerName; 105 | } 106 | 107 | /** 108 | * Consider if it is equal with another entity id. 109 | * 110 | * @param EntityId $entityId The entity id to compare with. 111 | * 112 | * @return bool 113 | */ 114 | public function equals(EntityId $entityId): bool 115 | { 116 | return ((string) $this == (string) $entityId); 117 | } 118 | 119 | /** 120 | * Cast entity id to string. 121 | * 122 | * @return string 123 | */ 124 | public function __toString(): string 125 | { 126 | return $this->providerName . '::' . $this->identifier; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Data/EntityManager.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Data; 16 | 17 | /** 18 | * Interface EntityManager create entity repositories. 19 | * 20 | * @package Netzmacht\Workflow\Data 21 | */ 22 | interface EntityManager 23 | { 24 | /** 25 | * Create an entity repository. 26 | * 27 | * @param string $providerName The provider name. 28 | * 29 | * @throws \InvalidArgumentException If repository could not be created. 30 | * 31 | * @return EntityRepository 32 | */ 33 | public function getRepository(string $providerName): EntityRepository; 34 | } 35 | -------------------------------------------------------------------------------- /src/Data/EntityRepository.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Data; 16 | 17 | /** 18 | * Interface EntityRepository describes the repository which stores the items. 19 | * 20 | * @package Netzmacht\Workflow\Data 21 | */ 22 | interface EntityRepository 23 | { 24 | /** 25 | * Find an entity by id. 26 | * 27 | * @param mixed $entityId The Entity id. 28 | * 29 | * @return mixed 30 | */ 31 | public function find($entityId); 32 | 33 | /** 34 | * Find multiple entities by a specification. 35 | * 36 | * @param Specification $specification The specification. 37 | * 38 | * @return iterable 39 | */ 40 | public function findBySpecification(Specification $specification): iterable; 41 | 42 | /** 43 | * Add an entity to the repository. 44 | * 45 | * @param mixed $entity The new entity. 46 | * 47 | * @return void 48 | */ 49 | public function add($entity): void; 50 | 51 | /** 52 | * Remove an entity from the repository. 53 | * 54 | * @param mixed $entity The entity. 55 | * 56 | * @return void 57 | */ 58 | public function remove($entity): void; 59 | } 60 | -------------------------------------------------------------------------------- /src/Data/Specification.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Data; 16 | 17 | /** 18 | * Data specification. 19 | * 20 | * @package Netzmacht\Workflow\Data 21 | */ 22 | interface Specification 23 | { 24 | /** 25 | * Check if an entity matches the specification. 26 | * 27 | * @param mixed $entity The entity. 28 | * 29 | * @return bool 30 | */ 31 | public function isSatisfiedBy($entity): bool; 32 | } 33 | -------------------------------------------------------------------------------- /src/Data/StateRepository.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Data; 16 | 17 | use Netzmacht\Workflow\Flow\State; 18 | 19 | /** 20 | * Interface StateRepository stores workflow states. 21 | * 22 | * @package Netzmacht\Workflow\Model 23 | */ 24 | interface StateRepository 25 | { 26 | /** 27 | * Find last workflow state of an entity. 28 | * 29 | * @param EntityId $entityId The entity id. 30 | * 31 | * @return State[]|iterable 32 | */ 33 | public function find(EntityId $entityId): iterable; 34 | 35 | /** 36 | * Add a new state. 37 | * 38 | * @param State $state The new state. 39 | * 40 | * @return void 41 | */ 42 | public function add(State $state): void; 43 | } 44 | -------------------------------------------------------------------------------- /src/Exception/WorkflowException.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Exception; 16 | 17 | /** 18 | * Class WorkflowException is thrown if something went wrong during workflow. 19 | * 20 | * @package Netzmacht\Workflow\Exception\Flow 21 | */ 22 | interface WorkflowException 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /src/Exception/WorkflowNotFound.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Exception; 16 | 17 | use Exception; 18 | use Netzmacht\Workflow\Data\EntityId; 19 | 20 | /** 21 | * Class WorkflowNotFound 22 | * 23 | * @package Netzmacht\Workflow\Exception 24 | */ 25 | class WorkflowNotFound extends \RuntimeException implements WorkflowException 26 | { 27 | /** 28 | * Create exception with the workflow name. 29 | * 30 | * @param string $workflowName Current workflow name. 31 | * @param int $code Error code. 32 | * @param Exception $previous Previous thrown exception. 33 | * 34 | * @return self 35 | */ 36 | public static function withName(string $workflowName, int $code = 0, Exception $previous = null) 37 | { 38 | return new self(sprintf('Workflow "%s" not found.', $workflowName), $code, $previous); 39 | } 40 | 41 | /** 42 | * Create exception with the workflow name. 43 | * 44 | * @param EntityId $entityId Entity id. 45 | * @param int $code Error code. 46 | * @param Exception $previous Previous thrown exception. 47 | * 48 | * @return self 49 | */ 50 | public static function forEntity(EntityId $entityId, int $code = 0, Exception $previous = null) 51 | { 52 | return new self(sprintf('No workflow found for entity "%s".', $entityId), $code, $previous); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Flow/Action.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | /** 18 | * Interface Action describes an action which is executed during transition. 19 | * 20 | * @package Netzmacht\Workflow 21 | */ 22 | interface Action 23 | { 24 | /** 25 | * Get the required payload properties. 26 | * 27 | * @param Item $item Workflow item. 28 | * 29 | * @return array 30 | */ 31 | public function getRequiredPayloadProperties(Item $item): array; 32 | 33 | /** 34 | * Validate the given item and context (payload properties). 35 | * 36 | * @param Item $item Workflow item. 37 | * @param Context $context Transition context. 38 | * 39 | * @return bool 40 | */ 41 | public function validate(Item $item, Context $context): bool; 42 | 43 | /** 44 | * Transit will execute the action. 45 | * 46 | * @param Transition $transition Current transition. 47 | * @param Item $item Workflow item. 48 | * @param Context $context Transition context. 49 | * 50 | * @return void 51 | */ 52 | public function transit(Transition $transition, Item $item, Context $context): void; 53 | } 54 | -------------------------------------------------------------------------------- /src/Flow/Base.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | /** 18 | * Class Configurable is the base class for each flow elements. 19 | * 20 | * @package Netzmacht\Workflow\Flow 21 | */ 22 | abstract class Base 23 | { 24 | /** 25 | * Configuration values. 26 | * 27 | * @var array 28 | */ 29 | private $config = array(); 30 | 31 | /** 32 | * Name of the element. 33 | * 34 | * @var string 35 | */ 36 | private $name; 37 | 38 | /** 39 | * Label of the element. 40 | * 41 | * @var string 42 | */ 43 | private $label; 44 | 45 | /** 46 | * Construct. 47 | * 48 | * @param string $name Name of the element. 49 | * @param string $label Label of the element. 50 | * @param array $config Configuration values. 51 | */ 52 | public function __construct(string $name, string $label = '', array $config = array()) 53 | { 54 | $this->name = $name; 55 | $this->label = $label ?: $name; 56 | $this->config = $config; 57 | } 58 | 59 | /** 60 | * Get element label. 61 | * 62 | * @return string 63 | */ 64 | public function getLabel(): string 65 | { 66 | return $this->label; 67 | } 68 | 69 | /** 70 | * Set the label. 71 | * 72 | * @param string $label The label. 73 | * 74 | * @return $this 75 | */ 76 | public function setLabel(string $label): self 77 | { 78 | $this->label = $label; 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get element name. 85 | * 86 | * @return string 87 | */ 88 | public function getName(): string 89 | { 90 | return $this->name; 91 | } 92 | 93 | /** 94 | * Set a config value. 95 | * 96 | * @param string $name Config property name. 97 | * @param mixed $value Config property value. 98 | * 99 | * @return $this 100 | */ 101 | public function setConfigValue(string $name, $value): self 102 | { 103 | $this->config[$name] = $value; 104 | 105 | return $this; 106 | } 107 | 108 | /** 109 | * Get a config value. 110 | * 111 | * @param string $name Config property name. 112 | * @param mixed $default Default value which is returned if config is not set. 113 | * 114 | * @return mixed 115 | */ 116 | public function getConfigValue(string $name, $default = null) 117 | { 118 | if (isset($this->config[$name])) { 119 | return $this->config[$name]; 120 | } 121 | 122 | return $default; 123 | } 124 | 125 | /** 126 | * Consider if config value isset. 127 | * 128 | * @param string $name Name of the config value. 129 | * 130 | * @return bool 131 | */ 132 | public function hasConfigValue(string $name): bool 133 | { 134 | return isset($this->config[$name]); 135 | } 136 | 137 | /** 138 | * Add multiple config properties. 139 | * 140 | * @param array $values Config values. 141 | * 142 | * @return $this 143 | */ 144 | public function addConfig(array $values): self 145 | { 146 | foreach ($values as $name => $value) { 147 | $this->setConfigValue($name, $value); 148 | } 149 | 150 | return $this; 151 | } 152 | 153 | /** 154 | * Remove a config property. 155 | * 156 | * @param string $name Config property name. 157 | * 158 | * @return $this 159 | */ 160 | public function removeConfigValue(string $name): self 161 | { 162 | unset($this->config[$name]); 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * Get configuration. 169 | * 170 | * @return array 171 | */ 172 | public function getConfig(): array 173 | { 174 | return $this->config; 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/Flow/Condition/Transition/AndCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Transition; 16 | 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | 21 | /** 22 | * Class AndCondition matches if all child conditions does. 23 | * 24 | * @package Netzmacht\Workflow\Flow\Condition\Transition 25 | */ 26 | class AndCondition extends ConditionCollection 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function match(Transition $transition, Item $item, Context $context): bool 32 | { 33 | $localContext = $context->createCleanCopy(); 34 | $success = true; 35 | 36 | foreach ($this->conditions as $condition) { 37 | if (!$condition->match($transition, $item, $localContext)) { 38 | $success = false; 39 | } 40 | } 41 | 42 | if (!$success) { 43 | $context->addError( 44 | 'transition.condition.and.failed', 45 | [], 46 | $localContext->getErrorCollection() 47 | ); 48 | } 49 | 50 | return $success; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Flow/Condition/Transition/Condition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Transition; 16 | 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | 21 | /** 22 | * Interface Condition describes an condition which used for transition conditions. 23 | * 24 | * @package Netzmacht\Workflow\Flow\Transition 25 | */ 26 | interface Condition 27 | { 28 | /** 29 | * Consider if condition matches for the given entity. 30 | * 31 | * @param Transition $transition The transition being in. 32 | * @param Item $item The entity being transits. 33 | * @param Context $context The transition context. 34 | * 35 | * @return bool 36 | */ 37 | public function match(Transition $transition, Item $item, Context $context): bool; 38 | } 39 | -------------------------------------------------------------------------------- /src/Flow/Condition/Transition/ConditionCollection.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Transition; 16 | 17 | use Assert\Assertion; 18 | 19 | /** 20 | * Class ConditionCollection contains child conditions which are called during match. 21 | * 22 | * @package Netzmacht\Workflow\Flow\Transition\Condition 23 | */ 24 | abstract class ConditionCollection implements Condition 25 | { 26 | /** 27 | * All child conditions of the collection. 28 | * 29 | * @var Condition[] 30 | */ 31 | protected $conditions = array(); 32 | 33 | /** 34 | * ConditionCollection constructor. 35 | * 36 | * @param Condition[]|iterable $conditions List of child conditions. 37 | */ 38 | public function __construct(iterable $conditions = []) 39 | { 40 | $this->addConditions($conditions); 41 | } 42 | 43 | /** 44 | * Add condition. 45 | * 46 | * @param Condition $condition Condition being added. 47 | * 48 | * @return $this 49 | */ 50 | public function addCondition(Condition $condition): self 51 | { 52 | $this->conditions[] = $condition; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Remove condition from collection. 59 | * 60 | * @param Condition $condition Condition being removed. 61 | * 62 | * @return $this 63 | */ 64 | public function removeCondition(Condition $condition): self 65 | { 66 | foreach ($this->conditions as $index => $value) { 67 | if ($value === $condition) { 68 | unset($this->conditions[$index]); 69 | } 70 | } 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Get child conditions. 77 | * 78 | * @return Condition[]|iterable 79 | */ 80 | public function getConditions(): iterable 81 | { 82 | return $this->conditions; 83 | } 84 | 85 | /** 86 | * Add multiple conditions. 87 | * 88 | * @param Condition[]|iterable $conditions Array of conditions being added. 89 | * 90 | * @return $this 91 | * 92 | * @throws \Assert\InvalidArgumentException If array contains an invalid condition. 93 | */ 94 | public function addConditions(iterable $conditions): self 95 | { 96 | Assertion::allIsInstanceOf($conditions, Condition::class); 97 | 98 | foreach ($conditions as $condition) { 99 | $this->addCondition($condition); 100 | } 101 | 102 | return $this; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Flow/Condition/Transition/OrCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Transition; 16 | 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | 21 | /** 22 | * Class OrCondition matches if any of the child condition matches. 23 | * 24 | * @package Netzmacht\Workflow\Flow\Condition\Transition 25 | */ 26 | class OrCondition extends ConditionCollection 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function match(Transition $transition, Item $item, Context $context): bool 32 | { 33 | if (empty($this->conditions)) { 34 | return true; 35 | } 36 | 37 | $localContext = $context->createCleanCopy(); 38 | 39 | foreach ($this->conditions as $condition) { 40 | if ($condition->match($transition, $item, $localContext)) { 41 | return true; 42 | } 43 | } 44 | 45 | $context->addError('transition.condition.or.failed', array(), $localContext->getErrorCollection()); 46 | 47 | return false; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Flow/Condition/Transition/PayloadPropertyCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Transition; 16 | 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Transition; 20 | use Netzmacht\Workflow\Util\Comparison; 21 | 22 | /** 23 | * Class PayloadPropertyCondition 24 | */ 25 | class PayloadPropertyCondition implements Condition 26 | { 27 | /** 28 | * Payload property name. 29 | * 30 | * @var string 31 | */ 32 | private $property; 33 | 34 | /** 35 | * Expected value. 36 | * 37 | * @var mixed 38 | */ 39 | private $value; 40 | 41 | /** 42 | * Comparison operator. 43 | * 44 | * @var string 45 | */ 46 | private $operator; 47 | 48 | /** 49 | * PayloadPropertyCondition constructor. 50 | * 51 | * @param string $property Payload property name. 52 | * @param mixed $value Expected value. 53 | * @param string $operator Comparison operator. 54 | */ 55 | public function __construct(string $property, $value, string $operator = Comparison::EQUALS) 56 | { 57 | $this->property = $property; 58 | $this->value = $value; 59 | $this->operator = $operator; 60 | } 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | public function match(Transition $transition, Item $item, Context $context): bool 66 | { 67 | $payloadValue = $context->getPayload()->get($this->property); 68 | 69 | if (Comparison::compare($payloadValue, $this->value, $this->operator)) { 70 | return true; 71 | } 72 | 73 | $context->addError( 74 | 'transition.condition.payload_property.failed', 75 | [ 76 | 'property' => $this->property, 77 | 'expected' => $this->value, 78 | 'actual' => $payloadValue, 79 | 'operator' => $this->operator, 80 | ] 81 | ); 82 | 83 | return false; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/AndCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * Class AndCondition matches if all child conditions matches. 22 | * 23 | * @package Netzmacht\Workflow\Flow\Workflow 24 | */ 25 | class AndCondition extends ConditionCollection 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool 31 | { 32 | foreach ($this->conditions as $condition) { 33 | if (!$condition->match($workflow, $entityId, $entity)) { 34 | return false; 35 | } 36 | } 37 | 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/Condition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * Interface Condition describes condition being used by the workflow. 22 | * 23 | * @package Netzmacht\Workflow\Flow\Workflow 24 | */ 25 | interface Condition 26 | { 27 | /** 28 | * Consider if workflow matches to the entity. 29 | * 30 | * @param Workflow $workflow The current workflow. 31 | * @param EntityId $entityId The entity id. 32 | * @param mixed $entity The entity. 33 | * 34 | * @return bool 35 | */ 36 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool; 37 | } 38 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/ConditionCollection.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Assert\Assertion; 18 | 19 | /** 20 | * Class ConditionCollection contains child condition which are called during match. 21 | * 22 | * @package Netzmacht\Workflow\Flow\Condition\Workflow 23 | */ 24 | abstract class ConditionCollection implements Condition 25 | { 26 | /** 27 | * Child conditions of the collection. 28 | * 29 | * @var Condition[]|iterable 30 | */ 31 | protected $conditions = array(); 32 | 33 | /** 34 | * Construct. 35 | * 36 | * @param Condition[]|iterable $conditions Conditions. 37 | */ 38 | public function __construct(iterable $conditions = array()) 39 | { 40 | $this->addConditions($conditions); 41 | } 42 | 43 | /** 44 | * Add new child condition. 45 | * 46 | * @param Condition $condition Child condition. 47 | * 48 | * @return $this 49 | */ 50 | public function addCondition(Condition $condition): self 51 | { 52 | $this->conditions[] = $condition; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Add multiple conditions. 59 | * 60 | * @param Condition[]|iterable $conditions Array of conditions. 61 | * 62 | * @return $this 63 | */ 64 | public function addConditions(iterable $conditions): self 65 | { 66 | Assertion::allIsInstanceOf($conditions, Condition::class); 67 | 68 | foreach ($conditions as $condition) { 69 | $this->addCondition($condition); 70 | } 71 | 72 | return $this; 73 | } 74 | 75 | /** 76 | * Get all conditions. 77 | * 78 | * @return Condition[]|iterable 79 | */ 80 | public function getConditions(): iterable 81 | { 82 | return $this->conditions; 83 | } 84 | 85 | /** 86 | * Remove condition from collection. 87 | * 88 | * @param Condition $condition Condition to remove. 89 | * 90 | * @return $this 91 | */ 92 | public function removeCondition(Condition $condition): self 93 | { 94 | foreach ($this->conditions as $index => $value) { 95 | if ($value === $condition) { 96 | unset($this->conditions[$index]); 97 | } 98 | } 99 | 100 | return $this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/ConfigValueCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * This condition checks a config parameter value 22 | * 23 | * @package Netzmacht\Contao\Workflow\Condition\Workflow 24 | */ 25 | class ConfigValueCondition implements Condition 26 | { 27 | /** 28 | * Name of the config parameter. 29 | * 30 | * @var string 31 | */ 32 | private $name; 33 | 34 | /** 35 | * Value of the config parameter. 36 | * 37 | * @var mixed 38 | */ 39 | private $value; 40 | 41 | /** 42 | * If true a strict comparison is made. 43 | * 44 | * @var bool 45 | */ 46 | private $strict; 47 | 48 | /** 49 | * ConfigCondition constructor. 50 | * 51 | * @param string $name Name of the config parameter. 52 | * @param mixed $value Value of the config parameter. 53 | * @param bool $strict If true a strict comparison is made. 54 | */ 55 | public function __construct(string $name, $value, bool $strict = false) 56 | { 57 | $this->name = $name; 58 | $this->value = $value; 59 | $this->strict = $strict; 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | */ 65 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool 66 | { 67 | $value = $workflow->getConfigValue($this->name); 68 | 69 | if ($this->strict) { 70 | return $this->value === $value; 71 | } 72 | 73 | return $this->value == $value; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/OrCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * Class OrCondition matches if any child conditions matches. 22 | * 23 | * @package Netzmacht\Workflow\Flow\Workflow 24 | */ 25 | class OrCondition extends ConditionCollection 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool 31 | { 32 | foreach ($this->conditions as $condition) { 33 | if ($condition->match($workflow, $entityId, $entity)) { 34 | return true; 35 | } 36 | } 37 | 38 | if (!$this->conditions) { 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Flow/Condition/Workflow/ProviderNameCondition.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Condition\Workflow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * Class ProviderTypeCondition check if entity matches a specific provider. 22 | * 23 | * @package Netzmacht\Workflow\Flow\Workflow\Condition 24 | */ 25 | class ProviderNameCondition implements Condition 26 | { 27 | /** 28 | * Provider name to check against. 29 | * 30 | * @var string 31 | */ 32 | private $providerName; 33 | 34 | /** 35 | * ProviderNameCondition constructor. 36 | * 37 | * @param string $providerName 38 | */ 39 | public function __construct(string $providerName) 40 | { 41 | $this->providerName = $providerName; 42 | } 43 | 44 | /** 45 | * Get the provider name to check against. 46 | * 47 | * @return string 48 | */ 49 | public function getProviderName(): string 50 | { 51 | return $this->providerName; 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function match(Workflow $workflow, EntityId $entityId, $entity): bool 58 | { 59 | if ($this->providerName) { 60 | return $entityId->getProviderName() === $this->providerName; 61 | } 62 | 63 | return $entityId->getProviderName() === $workflow->getProviderName(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Flow/Context.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 18 | use Netzmacht\Workflow\Flow\Context\Properties; 19 | 20 | /** 21 | * Class Context provides extra information for a transition. 22 | * 23 | * @package Netzmacht\Workflow\Flow 24 | */ 25 | class Context 26 | { 27 | const NAMESPACE_DEFAULT = 'default'; 28 | 29 | const NAMESPACE_ENTITY = 'entity'; 30 | 31 | /** 32 | * Properties which will be stored as state data. 33 | * 34 | * @var Properties 35 | */ 36 | private $properties; 37 | 38 | /** 39 | * Transition payload. 40 | * 41 | * @var Properties 42 | */ 43 | private $payload; 44 | 45 | /** 46 | * Error collection. 47 | * 48 | * @var ErrorCollection 49 | */ 50 | private $errorCollection; 51 | 52 | /** 53 | * Construct. 54 | * 55 | * @param Properties $properties The properties to be stored. 56 | * @param Properties $payload The given parameters. 57 | * @param ErrorCollection|null $errorCollection Error collection. 58 | */ 59 | public function __construct( 60 | Properties $properties = null, 61 | Properties $payload = null, 62 | ErrorCollection $errorCollection = null 63 | ) { 64 | $this->properties = $properties ?: new Properties(); 65 | $this->payload = $payload ?: new Properties(); 66 | $this->errorCollection = $errorCollection ?: new ErrorCollection(); 67 | } 68 | 69 | /** 70 | * Get properties. 71 | * 72 | * @return Properties 73 | */ 74 | public function getProperties() 75 | { 76 | return $this->properties; 77 | } 78 | 79 | /** 80 | * Get payload. 81 | * 82 | * @return Properties 83 | */ 84 | public function getPayload() 85 | { 86 | return $this->payload; 87 | } 88 | 89 | /** 90 | * Get error collection. 91 | * 92 | * @return ErrorCollection 93 | */ 94 | public function getErrorCollection(): ErrorCollection 95 | { 96 | return $this->errorCollection; 97 | } 98 | 99 | /** 100 | * Add an error. 101 | * 102 | * @param string $message Error message. 103 | * @param array $params Params for the error message. 104 | * @param ErrorCollection $collection Option. Child collection of the error. 105 | * 106 | * @return self 107 | */ 108 | public function addError(string $message, array $params = array(), ErrorCollection $collection = null): self 109 | { 110 | $this->errorCollection->addError($message, $params, $collection); 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * Get a new context with an empty error collection. 117 | * 118 | * @param array|null $payload Optional pass a new set of payload. 119 | * 120 | * @return Context 121 | */ 122 | public function createCleanCopy(array $payload = null): Context 123 | { 124 | if ($payload !== null) { 125 | $payload = new Properties($payload); 126 | } else { 127 | $payload = $this->payload; 128 | } 129 | 130 | return new Context($this->properties, $payload, new ErrorCollection()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Flow/Context/ErrorCollection.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Context; 16 | 17 | use Countable; 18 | use IteratorAggregate; 19 | 20 | /** 21 | * Class ErrorCollection collects error messages being raised during transition. 22 | * 23 | * @package Netzmacht\Workflow 24 | */ 25 | class ErrorCollection implements IteratorAggregate, Countable 26 | { 27 | /** 28 | * Stored errors. 29 | * 30 | * @var array 31 | */ 32 | private $errors = array(); 33 | 34 | /** 35 | * Construct. 36 | * 37 | * @param array $errors Initial error messages. 38 | */ 39 | public function __construct(array $errors = array()) 40 | { 41 | $this->addErrors($errors); 42 | } 43 | 44 | /** 45 | * Add a new error. 46 | * 47 | * @param string $message Error message. 48 | * @param array $params Params for the error message. 49 | * @param ErrorCollection $collection Option. Child collection of the error. 50 | * 51 | * @return $this 52 | */ 53 | public function addError(string $message, array $params = array(), ErrorCollection $collection = null) 54 | { 55 | $this->errors[] = array($message, $params, $collection); 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * Check if any error isset. 62 | * 63 | * @return bool 64 | */ 65 | public function hasErrors(): bool 66 | { 67 | return !empty($this->errors); 68 | } 69 | 70 | /** 71 | * Count error messages. 72 | * 73 | * @return int 74 | */ 75 | public function countErrors(): int 76 | { 77 | return count($this->errors); 78 | } 79 | 80 | /** 81 | * Get an error by it's index. 82 | * 83 | * @param int $index Error index. 84 | * 85 | * @throws \InvalidArgumentException If error index is not set. 86 | * 87 | * @return array 88 | */ 89 | public function getError(int $index): array 90 | { 91 | if (isset($this->errors[$index])) { 92 | return $this->errors[$index]; 93 | } 94 | 95 | throw new \InvalidArgumentException('Error with index "' . $index . '" not set.'); 96 | } 97 | 98 | /** 99 | * Reset error collection. 100 | * 101 | * @return $this 102 | */ 103 | public function reset(): self 104 | { 105 | $this->errors = array(); 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Add a set of errors. 112 | * 113 | * @param array $errors List of errors. 114 | * 115 | * @return $this 116 | */ 117 | public function addErrors(array $errors): self 118 | { 119 | foreach ($errors as $error) { 120 | list($message, $params, $collection) = (array) $error; 121 | 122 | $this->addError($message, $params, $collection); 123 | } 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * Get all errors. 130 | * 131 | * @return array 132 | */ 133 | public function getErrors(): array 134 | { 135 | return $this->errors; 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function getIterator(): iterable 142 | { 143 | return new \ArrayIterator($this->errors); 144 | } 145 | 146 | /** 147 | * {@inheritdoc} 148 | */ 149 | public function count(): int 150 | { 151 | return $this->countErrors(); 152 | } 153 | 154 | /** 155 | * Convert error collection to an array. 156 | * 157 | * @return array 158 | */ 159 | public function toArray(): array 160 | { 161 | return array_map( 162 | function ($error) { 163 | if ($error[2]) { 164 | /** @var ErrorCollection $collection */ 165 | $collection = $error[2]; 166 | $error[2] = $collection->toArray(); 167 | } 168 | 169 | return $error; 170 | }, 171 | $this->errors 172 | ); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Flow/Context/Properties.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Context; 16 | 17 | /** 18 | * Class Properties 19 | */ 20 | class Properties 21 | { 22 | /** 23 | * Properties. 24 | * 25 | * @var array 26 | */ 27 | private $properties = []; 28 | 29 | /** 30 | * Properties constructor. 31 | * 32 | * @param array $properties Properties. 33 | */ 34 | public function __construct(array $properties = []) 35 | { 36 | $this->properties = $properties; 37 | } 38 | 39 | /** 40 | * Get properties. 41 | * 42 | * @return array 43 | */ 44 | public function toArray(): array 45 | { 46 | return $this->properties; 47 | } 48 | 49 | /** 50 | * Check if a property exists. 51 | * 52 | * @param string $propertyName Name of the property. 53 | * 54 | * @return bool 55 | */ 56 | public function has(string $propertyName): bool 57 | { 58 | return array_key_exists($propertyName, $this->properties); 59 | } 60 | 61 | /** 62 | * Set a property value. 63 | * 64 | * @param string $propertyName Name of the property. 65 | * @param mixed $value Value of the property. 66 | * 67 | * @return Properties 68 | */ 69 | public function set(string $propertyName, $value): self 70 | { 71 | $this->properties[$propertyName] = $value; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Get the property value. If property does not exist, null is returned. 78 | * 79 | * @param string $propertyName Name of the property. 80 | * 81 | * @return mixed 82 | */ 83 | public function get(string $propertyName) 84 | { 85 | if (isset($this->properties[$propertyName])) { 86 | return $this->properties[$propertyName]; 87 | } 88 | 89 | return null; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Flow/Exception/ActionFailedException.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Exception; 16 | 17 | use function end; 18 | use Netzmacht\Workflow\Flow\Action; 19 | use Netzmacht\Workflow\Flow\Base; 20 | use Netzmacht\Workflow\Flow\Context\ErrorCollection; 21 | 22 | /** 23 | * Class TransactionActionFailed is thrown then a transaction action failed. 24 | * 25 | * @package Netzmacht\Workflow\Flow\Transition 26 | */ 27 | class ActionFailedException extends FlowException 28 | { 29 | /** 30 | * The action name. 31 | * 32 | * @var string|null 33 | */ 34 | private $actionName; 35 | 36 | /** 37 | * Additional error collection. 38 | * 39 | * @var ErrorCollection|null 40 | */ 41 | private $errorCollection; 42 | 43 | /** 44 | * Create exception for with an action name. 45 | * 46 | * @param string $actionName The action name. 47 | * @param ErrorCollection|null $errorCollection Additional error collection. 48 | * 49 | * @return ActionFailedException 50 | */ 51 | public static function namedAction(string $actionName, ?ErrorCollection $errorCollection = null): self 52 | { 53 | $exception = new self(sprintf('Execution of action "%s" failed.', $actionName)); 54 | $exception->actionName = $actionName; 55 | $exception->errorCollection = $errorCollection; 56 | 57 | return $exception; 58 | } 59 | 60 | /** 61 | * Create exception for an action. 62 | * 63 | * @param Action $action The action. 64 | * @param ErrorCollection|null $errorCollection Additional error collection. 65 | * 66 | * @return ActionFailedException 67 | */ 68 | public static function action(Action $action, ?ErrorCollection $errorCollection = null): self 69 | { 70 | if ($action instanceof Base) { 71 | $actionName = $action->getLabel(); 72 | } else { 73 | $parts = explode('\\', trim(get_class($action), '\\')); 74 | $actionName = end($parts); 75 | } 76 | 77 | return self::namedAction($actionName, $errorCollection); 78 | } 79 | 80 | /** 81 | * Get the action name. 82 | * 83 | * @return string|null 84 | */ 85 | public function actionName(): ?string 86 | { 87 | return $this->actionName; 88 | } 89 | 90 | /** 91 | * Get the error collection. 92 | * 93 | * @return ErrorCollection|null 94 | */ 95 | public function errorCollection(): ?ErrorCollection 96 | { 97 | return $this->errorCollection; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Flow/Exception/FlowException.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Exception; 16 | 17 | use Netzmacht\Workflow\Exception\WorkflowException; 18 | 19 | /** 20 | * Class FlowException. 21 | * 22 | * @package Netzmacht\Workflow\Flow\Exception 23 | */ 24 | class FlowException extends \RuntimeException implements WorkflowException 25 | { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/Flow/Exception/StepNotFoundException.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Exception; 16 | 17 | use Exception; 18 | 19 | /** 20 | * Class StepNotFoundException is thrown when step is not found. 21 | * 22 | * @package Netzmacht\Workflow\Flow\Exception 23 | */ 24 | class StepNotFoundException extends FlowException 25 | { 26 | /** 27 | * Construct. 28 | * 29 | * @param string $stepName The step name which is not found. 30 | * @param string $workflowName Current workflow name. 31 | * @param int $code Error code. 32 | * @param Exception $previous Previous thrown exception. 33 | */ 34 | public function __construct(string $stepName, string $workflowName, int $code = 0, Exception $previous = null) 35 | { 36 | parent::__construct( 37 | sprintf('Step "%s" is not part of workflow "%s"', $stepName, $workflowName), 38 | $code, 39 | $previous 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Flow/Exception/TransitionNotFound.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Exception; 16 | 17 | use Exception; 18 | 19 | /** 20 | * Class TransitionNotFoundException is thrown then transition was not found. 21 | * 22 | * @package Netzmacht\Workflow\Flow\Exception 23 | */ 24 | class TransitionNotFound extends FlowException 25 | { 26 | /** 27 | * Construct. 28 | * 29 | * @param string $transitionName The not found transition name. 30 | * @param string $workflowName Current workflow name. 31 | * @param int $code Error code. 32 | * @param Exception $previous Previous thrown exception. 33 | * 34 | * @return TransitionNotFound 35 | */ 36 | public static function withName( 37 | string $transitionName, 38 | string $workflowName, 39 | int $code = 0, 40 | Exception $previous = null 41 | ) { 42 | return new self( 43 | sprintf('Transition "%s" not found in workflow "%s"', $transitionName, $workflowName), 44 | $code, 45 | $previous 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Flow/Security/Permission.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow\Security; 16 | 17 | use Assert\Assertion; 18 | use Netzmacht\Workflow\Flow\Workflow; 19 | 20 | /** 21 | * Class Permission describes a permission in a workflow. 22 | * 23 | * @package Netzmacht\Workflow\Security 24 | */ 25 | class Permission 26 | { 27 | /** 28 | * The workflow name. 29 | * 30 | * @var string 31 | */ 32 | private $workflowName; 33 | 34 | /** 35 | * The permission id. 36 | * 37 | * @var string 38 | */ 39 | private $permissionId; 40 | 41 | /** 42 | * Construct. 43 | * 44 | * @param string $workflowName The workflow name. 45 | * @param string $permissionId The permission id. 46 | */ 47 | protected function __construct(string $workflowName, string $permissionId) 48 | { 49 | $this->workflowName = $workflowName; 50 | $this->permissionId = $permissionId; 51 | } 52 | 53 | /** 54 | * Create a permission for a workflow. 55 | * 56 | * @param Workflow $workflow Workflow to which the permission belongs to. 57 | * @param string $permissionId The permission id. 58 | * 59 | * @return static 60 | */ 61 | public static function forWorkflow(Workflow $workflow, string $permissionId): self 62 | { 63 | return static::forWorkflowName($workflow->getName(), $permissionId); 64 | } 65 | 66 | /** 67 | * Reconstruct permission from a string representation. 68 | * 69 | * @param string $workflowName Workflow name. 70 | * @param string $permissionId Permission id. 71 | * 72 | * @return static 73 | */ 74 | public static function forWorkflowName(string $workflowName, string $permissionId): self 75 | { 76 | Assertion::notBlank($workflowName); 77 | Assertion::notBlank($permissionId); 78 | 79 | self::guardValidPermission($workflowName, $permissionId); 80 | 81 | return new static($workflowName, $permissionId); 82 | } 83 | 84 | /** 85 | * Reconstruct permission from a string representation. 86 | * 87 | * @param string $permission Permission string representation. 88 | * 89 | * @return static 90 | */ 91 | public static function fromString(string $permission): self 92 | { 93 | list($workflowName, $permissionId) = explode(':', $permission); 94 | 95 | $message = sprintf( 96 | 'Invalid permission string given. Expected "workflowName:permissionId, got "%s"".', 97 | $permission 98 | ); 99 | 100 | self::guardValidPermission($workflowName, $permissionId, $message); 101 | 102 | return new static($workflowName, $permissionId); 103 | } 104 | 105 | /** 106 | * Get the permission id. 107 | * 108 | * @return string 109 | */ 110 | public function getPermissionId(): string 111 | { 112 | return $this->permissionId; 113 | } 114 | 115 | /** 116 | * Get the workflow name. 117 | * 118 | * @return string 119 | */ 120 | public function getWorkflowName(): string 121 | { 122 | return $this->workflowName; 123 | } 124 | 125 | /** 126 | * Cast permission to a string representation. 127 | * 128 | * @return string 129 | */ 130 | public function __toString(): string 131 | { 132 | return $this->workflowName . ':' . $this->permissionId; 133 | } 134 | 135 | /** 136 | * Consider if permission equals with another one. 137 | * 138 | * @param Permission $permission Permission to check against. 139 | * 140 | * @return bool 141 | */ 142 | public function equals(Permission $permission): bool 143 | { 144 | return ((string) $this === (string) $permission); 145 | } 146 | 147 | /** 148 | * Guard that permission values are valid. 149 | * 150 | * @param string $workflowName The workflow name. 151 | * @param string $permissionId The permission id. 152 | * @param string|null $message Optional error message. 153 | * 154 | * @return void 155 | */ 156 | protected static function guardValidPermission( 157 | string $workflowName, 158 | string $permissionId, 159 | string $message = null 160 | ): void { 161 | Assertion::notBlank($workflowName, $message); 162 | Assertion::notBlank($permissionId, $message); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Flow/State.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | use DateTimeImmutable; 18 | use Netzmacht\Workflow\Data\EntityId; 19 | use Netzmacht\Workflow\Flow\Exception\FlowException; 20 | 21 | /** 22 | * Class State stores information of a current state of an entity. 23 | * 24 | * @package Netzmacht\Workflow\Flow 25 | */ 26 | class State 27 | { 28 | /** 29 | * The state id. 30 | * 31 | * @var int 32 | */ 33 | private $stateId; 34 | 35 | /** 36 | * The entity id. 37 | * 38 | * @var EntityId 39 | */ 40 | private $entityId; 41 | 42 | /** 43 | * Store if transition was successful. 44 | * 45 | * @var bool 46 | */ 47 | private $successful; 48 | 49 | /** 50 | * The last transition. 51 | * 52 | * @var string 53 | */ 54 | private $transitionName; 55 | 56 | /** 57 | * The current step. 58 | * 59 | * @var string 60 | */ 61 | private $stepName; 62 | 63 | /** 64 | * Date being stored. 65 | * 66 | * @var array 67 | */ 68 | private $data = array(); 69 | 70 | /** 71 | * Date when state was reached. 72 | * 73 | * @var DateTimeImmutable 74 | */ 75 | private $reachedAt; 76 | 77 | /** 78 | * List of errors. 79 | * 80 | * @var array 81 | */ 82 | private $errors; 83 | 84 | /** 85 | * Name of start workflow. 86 | * 87 | * @var string 88 | */ 89 | private $startWorkflowName; 90 | 91 | /** 92 | * Name of the target workflow. 93 | * 94 | * @var string 95 | */ 96 | private $targetWorkflowName; 97 | 98 | /** 99 | * Construct. 100 | * 101 | * @param EntityId $entityId The entity id. 102 | * @param string $startWorkflowName Workflow name of the start point. 103 | * @param string $transitionName The transition executed to reach the step. 104 | * @param string $stepToName The step reached after transition. 105 | * @param bool $successful Consider if transition was successful. 106 | * @param array $data Stored data. 107 | * @param DateTimeImmutable $reachedAt Time when state was reached. 108 | * @param array $errors List of errors. 109 | * @param int $stateId The state id of a persisted state. 110 | * @param string|null $targetWorkflowName Workflow name of the target point. Allow null for BC reasons. 111 | * 112 | * @SuppressWarnings(PHPMD.ExcessiveParameterList) 113 | */ 114 | public function __construct( 115 | EntityId $entityId, 116 | string $startWorkflowName, 117 | string $transitionName, 118 | string $stepToName, 119 | bool $successful, 120 | array $data, 121 | DateTimeImmutable $reachedAt, 122 | array $errors = array(), 123 | int $stateId = null, 124 | ?string $targetWorkflowName = null 125 | ) { 126 | $this->entityId = $entityId; 127 | $this->startWorkflowName = $startWorkflowName; 128 | $this->transitionName = $transitionName; 129 | $this->stepName = $stepToName; 130 | $this->successful = $successful; 131 | $this->data = $data; 132 | $this->reachedAt = $reachedAt; 133 | $this->errors = $errors; 134 | $this->stateId = $stateId; 135 | $this->targetWorkflowName = $targetWorkflowName ?: $startWorkflowName; 136 | } 137 | 138 | /** 139 | * Create an initial state. 140 | * 141 | * @param EntityId $entityId The entity id. 142 | * @param Transition $transition The current executed transition. 143 | * @param Context $context The context. 144 | * @param bool $success Success state. 145 | * 146 | * @return State 147 | * 148 | * @throws FlowException When transition has no target step. 149 | */ 150 | public static function start( 151 | EntityId $entityId, 152 | Transition $transition, 153 | Context $context, 154 | $success 155 | ) { 156 | $stepTo = $transition->getStepTo(); 157 | 158 | if ($stepTo === null) { 159 | throw new FlowException( 160 | sprintf('Failed to start workflow. Transition "%s" has no target step', $transition->getName()) 161 | ); 162 | } 163 | 164 | $workflowName = $stepTo->getWorkflowName() ?: $transition->getWorkflow()->getName(); 165 | $state = new State( 166 | $entityId, 167 | $workflowName, 168 | $transition->getName(), 169 | $stepTo->getName(), 170 | $success, 171 | $context->getProperties()->toArray(), 172 | new \DateTimeImmutable(), 173 | $context->getErrorCollection()->toArray(), 174 | null, 175 | $workflowName 176 | ); 177 | 178 | return $state; 179 | } 180 | 181 | /** 182 | * Get step name. 183 | * 184 | * @return string 185 | */ 186 | public function getStepName(): string 187 | { 188 | return $this->stepName; 189 | } 190 | 191 | /** 192 | * Get transition name. 193 | * 194 | * @return string 195 | */ 196 | public function getTransitionName(): string 197 | { 198 | return $this->transitionName; 199 | } 200 | 201 | /** 202 | * Get the current workflow name. 203 | * 204 | * If the state transition was successful the target workflow name is returned, otherwise the start workflow name. 205 | * 206 | * @return string 207 | */ 208 | public function getWorkflowName(): string 209 | { 210 | if ($this->isSuccessful()) { 211 | return $this->getTargetWorkflowName(); 212 | } 213 | 214 | return $this->getStartWorkflowName(); 215 | } 216 | 217 | /** 218 | * Get start workflow name. 219 | * 220 | * @return string 221 | */ 222 | public function getStartWorkflowName(): string 223 | { 224 | return $this->startWorkflowName; 225 | } 226 | 227 | /** 228 | * Get target workflow name. 229 | * 230 | * @return string 231 | */ 232 | public function getTargetWorkflowName(): string 233 | { 234 | return $this->targetWorkflowName; 235 | } 236 | 237 | /** 238 | * Get state data. 239 | * 240 | * @return array 241 | */ 242 | public function getData(): array 243 | { 244 | return $this->data; 245 | } 246 | 247 | /** 248 | * Get reached at time. 249 | * 250 | * @return DateTimeImmutable 251 | */ 252 | public function getReachedAt(): \DateTimeImmutable 253 | { 254 | return $this->reachedAt; 255 | } 256 | 257 | /** 258 | * Consider if state is successful. 259 | * 260 | * @return bool 261 | */ 262 | public function isSuccessful(): bool 263 | { 264 | return $this->successful; 265 | } 266 | 267 | /** 268 | * Get the entity id. 269 | * 270 | * @return EntityId 271 | */ 272 | public function getEntityId(): EntityId 273 | { 274 | return $this->entityId; 275 | } 276 | 277 | /** 278 | * Get error messages. 279 | * 280 | * @return array 281 | */ 282 | public function getErrors(): array 283 | { 284 | return $this->errors; 285 | } 286 | 287 | /** 288 | * Get state id. 289 | * 290 | * @return int|null 291 | */ 292 | public function getStateId():? int 293 | { 294 | return $this->stateId; 295 | } 296 | 297 | /** 298 | * Transit to a new state. 299 | * 300 | * @param Transition $transition The transition being performed. 301 | * @param Context $context The transition context. 302 | * @param bool $success The success state. 303 | * 304 | * @return State 305 | * 306 | * @throws FlowException When transition fails. 307 | */ 308 | public function transit( 309 | Transition $transition, 310 | Context $context, 311 | bool $success = true 312 | ): State { 313 | $dateTime = new DateTimeImmutable(); 314 | $stepName = $this->stepName; 315 | $workflowName = $this->getWorkflowName(); 316 | $targetWorkflowName = $workflowName; 317 | 318 | if ($success) { 319 | $stepTo = $transition->getStepTo(); 320 | if ($stepTo === null) { 321 | throw new FlowException( 322 | sprintf('Failed to transit state. Transition "%s" has no target step', $transition->getName()) 323 | ); 324 | } 325 | 326 | $targetWorkflowName = $stepTo->getWorkflowName() ?: $transition->getWorkflow()->getName(); 327 | $stepName = $stepTo->getName(); 328 | } 329 | 330 | $properties = $context->getProperties(); 331 | 332 | return new static( 333 | $this->entityId, 334 | $workflowName, 335 | $transition->getName(), 336 | $stepName, 337 | $success, 338 | $properties ? $properties->toArray() : [], 339 | $dateTime, 340 | $context->getErrorCollection()->toArray(), 341 | null, 342 | $targetWorkflowName 343 | ); 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /src/Flow/Step.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | use Netzmacht\Workflow\Flow\Security\Permission; 18 | 19 | /** 20 | * Class Step defines fixed step in the workflow process. 21 | * 22 | * @package Netzmacht\Workflow\Flow 23 | */ 24 | class Step extends Base 25 | { 26 | /** 27 | * The allowed transition names. 28 | * 29 | * @var array 30 | */ 31 | private $allowedTransitions = array(); 32 | 33 | /** 34 | * Step is a final step. 35 | * 36 | * @var bool 37 | */ 38 | private $final = false; 39 | 40 | /** 41 | * Assigned permission. 42 | * 43 | * @var Permission|null 44 | */ 45 | private $permission; 46 | 47 | /** 48 | * The workflow name. 49 | * 50 | * For BC reasons it might be null. 51 | * 52 | * @var string|null 53 | */ 54 | private $workflowName; 55 | 56 | /** 57 | * Construct. 58 | * 59 | * @param string $name Name of the element. 60 | * @param string $label Label of the element. 61 | * @param array $config Configuration values. 62 | * @param string|null $workflowName Name of the corresponding workflow. For BC reasons it might be null. 63 | */ 64 | public function __construct(string $name, string $label = '', array $config = [], ?string $workflowName = null) 65 | { 66 | parent::__construct($name, $label, $config); 67 | 68 | $this->workflowName = $workflowName; 69 | } 70 | 71 | /** 72 | * Consider if step is final. 73 | * 74 | * @return bool 75 | */ 76 | public function isFinal(): bool 77 | { 78 | return $this->final; 79 | } 80 | 81 | /** 82 | * Mark step as final. 83 | * 84 | * @param bool $final Step is a final step. 85 | * 86 | * @return $this 87 | */ 88 | public function setFinal(bool $final): self 89 | { 90 | $this->final = $final; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * Allow a transition. 97 | * 98 | * @param string $transitionName The name of the allowed transition. 99 | * 100 | * @return $this 101 | */ 102 | public function allowTransition(string $transitionName): self 103 | { 104 | if (!in_array($transitionName, $this->allowedTransitions)) { 105 | $this->allowedTransitions[] = $transitionName; 106 | } 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Get workflow name. 113 | * 114 | * @return string|null 115 | */ 116 | public function getWorkflowName(): ?string 117 | { 118 | return $this->workflowName; 119 | } 120 | 121 | /** 122 | * Disallow a transition. 123 | * 124 | * @param string $transitionName The name of the disallowed transition. 125 | * 126 | * @return $this 127 | */ 128 | public function disallowTransition(string $transitionName): self 129 | { 130 | $key = array_search($transitionName, $this->allowedTransitions); 131 | 132 | if ($key !== false) { 133 | unset($this->allowedTransitions[$key]); 134 | $this->allowedTransitions = array_values($this->allowedTransitions); 135 | } 136 | 137 | return $this; 138 | } 139 | 140 | /** 141 | * Get all allowed transition names. 142 | * 143 | * @return array 144 | */ 145 | public function getAllowedTransitions(): array 146 | { 147 | if ($this->isFinal()) { 148 | return array(); 149 | } 150 | 151 | return $this->allowedTransitions; 152 | } 153 | 154 | /** 155 | * Consider if transition is allowed. 156 | * 157 | * @param string $transitionName The name of the checked transition. 158 | * 159 | * @return bool 160 | */ 161 | public function isTransitionAllowed(string $transitionName): bool 162 | { 163 | if ($this->isFinal()) { 164 | return false; 165 | } 166 | 167 | return in_array($transitionName, $this->allowedTransitions); 168 | } 169 | 170 | /** 171 | * Consider if step has a specific permission. 172 | * 173 | * @param Permission $permission Permission to be checked. 174 | * 175 | * @return bool 176 | */ 177 | public function hasPermission(Permission $permission): bool 178 | { 179 | if ($this->permission) { 180 | return $this->permission->equals($permission); 181 | } 182 | 183 | return false; 184 | } 185 | 186 | /** 187 | * Get permission of the step. If none is assigned it returns null. 188 | * 189 | * @return Permission|null 190 | */ 191 | public function getPermission():? Permission 192 | { 193 | return $this->permission; 194 | } 195 | 196 | /** 197 | * Set permission. 198 | * 199 | * @param Permission $permission Permission to be set. 200 | * 201 | * @return $this 202 | */ 203 | public function setPermission(Permission $permission): self 204 | { 205 | $this->permission = $permission; 206 | 207 | return $this; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/Flow/Workflow.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Flow; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Condition\Workflow\AndCondition; 19 | use Netzmacht\Workflow\Flow\Condition\Workflow\Condition; 20 | use Netzmacht\Workflow\Flow\Exception\StepNotFoundException; 21 | use Netzmacht\Workflow\Flow\Exception\TransitionNotFound; 22 | 23 | /** 24 | * Class Workflow stores all information of a step processing workflow. 25 | * 26 | * @package Netzmacht\Workflow\Flow 27 | */ 28 | class Workflow extends Base 29 | { 30 | /** 31 | * Transitions being available in the workflow. 32 | * 33 | * @var Transition[] 34 | */ 35 | private $transitions = array(); 36 | 37 | /** 38 | * Steps being available in the workflow. 39 | * 40 | * @var Step[] 41 | */ 42 | private $steps = array(); 43 | 44 | /** 45 | * The start transition. 46 | * 47 | * @var Transition 48 | */ 49 | private $startTransition; 50 | 51 | /** 52 | * Condition to supports if workflow can handle an entity. 53 | * 54 | * @var AndCondition 55 | */ 56 | private $condition; 57 | 58 | /** 59 | * Name of the provider. 60 | * 61 | * @var string 62 | */ 63 | private $providerName; 64 | 65 | /** 66 | * Construct. 67 | * 68 | * @param string $name The name of the workflow. 69 | * @param string $providerName Name of the provider. 70 | * @param string $label The label of the workflow. 71 | * @param array $config Extra config. 72 | */ 73 | public function __construct(string $name, string $providerName, string $label = '', array $config = array()) 74 | { 75 | parent::__construct($name, $label, $config); 76 | 77 | $this->providerName = $providerName; 78 | } 79 | 80 | /** 81 | * Add a transition to the workflow. 82 | * 83 | * @param Transition $transition Transition to be added. 84 | * @param bool $startTransition True if transition will be the start transition. 85 | * 86 | * @return $this 87 | */ 88 | public function addTransition(Transition $transition, $startTransition = false): self 89 | { 90 | if (in_array($transition, $this->transitions)) { 91 | return $this; 92 | } 93 | 94 | $this->transitions[] = $transition; 95 | 96 | if ($startTransition) { 97 | $this->startTransition = $transition; 98 | } 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * Get a transition by name. 105 | * 106 | * @param string $transitionName The name of the transition. 107 | * 108 | * @throws TransitionNotFound If transition is not found. 109 | * 110 | * @return \Netzmacht\Workflow\Flow\Transition If transition is not found. 111 | */ 112 | public function getTransition(string $transitionName): Transition 113 | { 114 | foreach ($this->transitions as $transition) { 115 | if ($transition->getName() == $transitionName) { 116 | return $transition; 117 | } 118 | } 119 | 120 | throw TransitionNotFound::withName($transitionName, $this->getName()); 121 | } 122 | 123 | /** 124 | * Get allowed transitions for a workflow item. 125 | * 126 | * @param Item $item Workflow item. 127 | * @param Context $context Transition context. 128 | * 129 | * @throws StepNotFoundException If Step does not exists. 130 | * @throws TransitionNotFound If transition does not exists. 131 | * 132 | * @return Transition[]|iterable 133 | */ 134 | public function getAvailableTransitions(Item $item, Context $context = null): iterable 135 | { 136 | if ($context) { 137 | $context = $context->createCleanCopy(); 138 | } else { 139 | $context = new Context(); 140 | } 141 | 142 | if (!$item->isWorkflowStarted() || $item->getWorkflowName() !== $this->getName()) { 143 | $transitions = array($this->getStartTransition()); 144 | } else { 145 | $step = $this->getStep($item->getCurrentStepName()); 146 | $transitions = array_map( 147 | function ($transitionName) { 148 | return $this->getTransition($transitionName); 149 | }, 150 | $step->getAllowedTransitions() 151 | ); 152 | } 153 | 154 | return array_values( 155 | array_filter( 156 | $transitions, 157 | function (Transition $transition) use ($item, $context) { 158 | return $transition->isAvailable($item, $context); 159 | } 160 | ) 161 | ); 162 | } 163 | 164 | /** 165 | * Get all transitions. 166 | * 167 | * @return Transition[]|iterable 168 | */ 169 | public function getTransitions(): iterable 170 | { 171 | return $this->transitions; 172 | } 173 | 174 | /** 175 | * Check if transition is part of the workflow. 176 | * 177 | * @param string $transitionName Transition name. 178 | * 179 | * @return bool 180 | */ 181 | public function hasTransition(string $transitionName): bool 182 | { 183 | foreach ($this->transitions as $transition) { 184 | if ($transition->getName() === $transitionName) { 185 | return true; 186 | } 187 | } 188 | 189 | return false; 190 | } 191 | 192 | /** 193 | * Check if a specific transition is available. 194 | * 195 | * @param Item $item The workflow item. 196 | * @param Context $context Transition context. 197 | * @param string $transitionName The transition name. 198 | * 199 | * @return bool 200 | */ 201 | public function isTransitionAvailable( 202 | Item $item, 203 | Context $context, 204 | string $transitionName 205 | ): bool { 206 | if (!$item->isWorkflowStarted()) { 207 | return $this->getStartTransition()->getName() === $transitionName; 208 | } 209 | 210 | $step = $this->getStep($item->getCurrentStepName()); 211 | if (!$step->isTransitionAllowed($transitionName)) { 212 | return false; 213 | } 214 | 215 | $transition = $this->getTransition($transitionName); 216 | 217 | return $transition->isAvailable($item, $context); 218 | } 219 | 220 | /** 221 | * Add a new step to the workflow. 222 | * 223 | * @param Step $step Step to be added. 224 | * 225 | * @return $this 226 | */ 227 | public function addStep(Step $step): self 228 | { 229 | $this->steps[] = $step; 230 | 231 | return $this; 232 | } 233 | 234 | /** 235 | * Get a step by step name. 236 | * 237 | * @param string $stepName The step name. 238 | * 239 | * @return Step 240 | * 241 | * @throws StepNotFoundException If step is not found. 242 | */ 243 | public function getStep(string $stepName): Step 244 | { 245 | foreach ($this->steps as $step) { 246 | if ($step->getName() == $stepName) { 247 | return $step; 248 | } 249 | } 250 | 251 | throw new StepNotFoundException($stepName, $this->getName()); 252 | } 253 | 254 | /** 255 | * Check if step with a name exist. 256 | * 257 | * @param string $stepName The step name. 258 | * 259 | * @return bool 260 | */ 261 | public function hasStep(string $stepName): bool 262 | { 263 | foreach ($this->steps as $step) { 264 | if ($step->getName() == $stepName) { 265 | return true; 266 | } 267 | } 268 | 269 | return false; 270 | } 271 | 272 | /** 273 | * Set transition as start transition. 274 | * 275 | * @param string $transitionName Name of start transition. 276 | * 277 | * @throws TransitionNotFound If transition is not part of the workflow. 278 | * 279 | * @return $this 280 | */ 281 | public function setStartTransition(string $transitionName): self 282 | { 283 | $this->startTransition = $this->getTransition($transitionName); 284 | 285 | return $this; 286 | } 287 | 288 | /** 289 | * Get the start transition. 290 | * 291 | * @return Transition 292 | */ 293 | public function getStartTransition(): Transition 294 | { 295 | return $this->startTransition; 296 | } 297 | 298 | /** 299 | * Get the current condition. 300 | * 301 | * @return AndCondition 302 | */ 303 | public function getCondition():? AndCondition 304 | { 305 | return $this->condition; 306 | } 307 | 308 | /** 309 | * Shortcut to add a condition to the condition collection. 310 | * 311 | * @param Condition $condition Condition to be added. 312 | * 313 | * @return $this 314 | */ 315 | public function addCondition(Condition $condition) 316 | { 317 | if (!$this->condition) { 318 | $this->condition = new AndCondition(); 319 | } 320 | 321 | $this->condition->addCondition($condition); 322 | 323 | return $this; 324 | } 325 | 326 | /** 327 | * Get provider name. 328 | * 329 | * @return string 330 | */ 331 | public function getProviderName(): string 332 | { 333 | return $this->providerName; 334 | } 335 | 336 | /** 337 | * Consider if workflow is supports an entity. 338 | * 339 | * @param EntityId $entityId The entity id. 340 | * @param mixed $entity The entity. 341 | * 342 | * @return bool 343 | */ 344 | public function supports(EntityId $entityId, $entity): bool 345 | { 346 | if (!$this->condition) { 347 | return true; 348 | } 349 | 350 | return $this->condition->match($this, $entityId, $entity); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/Handler/AbstractTransitionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Handler; 16 | 17 | use Netzmacht\Workflow\Flow\Context; 18 | use Netzmacht\Workflow\Flow\Exception\FlowException; 19 | use Netzmacht\Workflow\Flow\Item; 20 | use Netzmacht\Workflow\Flow\State; 21 | use Netzmacht\Workflow\Flow\Step; 22 | use Netzmacht\Workflow\Flow\Transition; 23 | use Netzmacht\Workflow\Flow\Workflow; 24 | use Netzmacht\Workflow\Transaction\TransactionHandler; 25 | 26 | /** 27 | * AbstractTransitionHandler can be used as base class for transition handler implementations. 28 | * 29 | * @package Netzmacht\Workflow\Handler 30 | */ 31 | abstract class AbstractTransitionHandler implements TransitionHandler 32 | { 33 | /** 34 | * The given entity. 35 | * 36 | * @var Item 37 | */ 38 | private $item; 39 | 40 | /** 41 | * The current workflow. 42 | * 43 | * @var Workflow 44 | */ 45 | private $workflow; 46 | 47 | /** 48 | * The transition name which will be handled. 49 | * 50 | * @var string 51 | */ 52 | private $transitionName; 53 | 54 | /** 55 | * Validation state. 56 | * 57 | * @var bool 58 | */ 59 | private $validated; 60 | 61 | /** 62 | * The transaction handler. 63 | * 64 | * @var TransactionHandler 65 | */ 66 | protected $transactionHandler; 67 | 68 | /** 69 | * The transition context. 70 | * 71 | * @var Context 72 | */ 73 | private $context; 74 | 75 | /** 76 | * Construct. 77 | * 78 | * @param Item $item The item. 79 | * @param Workflow $workflow The current workflow. 80 | * @param string $transitionName The transition to be handled. 81 | * @param TransactionHandler $transactionHandler TransactionHandler take care of transactions. 82 | * 83 | * @throws FlowException If invalid transition name is given. 84 | */ 85 | public function __construct( 86 | Item $item, 87 | Workflow $workflow, 88 | $transitionName, 89 | TransactionHandler $transactionHandler 90 | ) { 91 | $this->item = $item; 92 | $this->workflow = $workflow; 93 | $this->transitionName = $transitionName; 94 | $this->transactionHandler = $transactionHandler; 95 | $this->context = new Context(); 96 | 97 | $this->guardAllowedTransition($transitionName); 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getTransition(): Transition 104 | { 105 | if ($this->isWorkflowStarted()) { 106 | return $this->workflow->getTransition($this->transitionName); 107 | } 108 | 109 | return $this->workflow->getStartTransition(); 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | public function getWorkflow(): Workflow 116 | { 117 | return $this->workflow; 118 | } 119 | 120 | /** 121 | * {@inheritdoc} 122 | */ 123 | public function getItem(): Item 124 | { 125 | return $this->item; 126 | } 127 | 128 | /** 129 | * {@inheritdoc} 130 | */ 131 | public function getContext(): Context 132 | { 133 | return $this->context; 134 | } 135 | 136 | /** 137 | * {@inheritdoc} 138 | */ 139 | public function isWorkflowStarted(): bool 140 | { 141 | return $this->item->isWorkflowStarted(); 142 | } 143 | 144 | /** 145 | * {@inheritdoc} 146 | */ 147 | public function getRequiredPayloadProperties(): array 148 | { 149 | return $this->getTransition()->getRequiredPayloadProperties($this->item); 150 | } 151 | 152 | /** 153 | * Consider if transition is available. 154 | * 155 | * @return bool 156 | */ 157 | public function isAvailable(): bool 158 | { 159 | return $this->getTransition()->isAvailable($this->item, $this->context); 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | public function getCurrentStep():? Step 166 | { 167 | if ($this->isWorkflowStarted()) { 168 | $stepName = $this->item->getCurrentStepName(); 169 | 170 | return $this->workflow->getStep($stepName); 171 | } 172 | 173 | return null; 174 | } 175 | 176 | /** 177 | * {@inheritdoc} 178 | */ 179 | public function validate(array $payload = []): bool 180 | { 181 | // first build the form 182 | $this->context = $this->context->createCleanCopy($payload); 183 | $this->validated = false; 184 | $transition = $this->getTransition(); 185 | 186 | // check pre conditions first 187 | if ($transition->checkPreCondition($this->item, $this->context)) { 188 | $this->validated = true; 189 | } 190 | 191 | // Validate the actions 192 | if (!$transition->validate($this->item, $this->context)) { 193 | $this->validated = false; 194 | } 195 | 196 | if ($this->validated && !$transition->checkCondition($this->item, $this->context)) { 197 | $this->validated = false; 198 | } 199 | 200 | return $this->validated; 201 | } 202 | 203 | /** 204 | * Execute the transition. 205 | * 206 | * @return State 207 | */ 208 | protected function executeTransition(): State 209 | { 210 | return $this->getTransition()->execute($this->item, $this->context); 211 | } 212 | 213 | /** 214 | * Guard that transition was validated before. 215 | * 216 | * @throws FlowException If transition. 217 | * 218 | * @return void 219 | */ 220 | protected function guardValidated(): void 221 | { 222 | if ($this->validated === null) { 223 | throw new FlowException('Transition was not validated so far.'); 224 | } elseif (!$this->validated) { 225 | throw new FlowException('Transition is in a invalid state and can\'t be processed.'); 226 | } 227 | } 228 | 229 | /** 230 | * Guard that requested transition is allowed. 231 | * 232 | * @param string|null $transitionName Transition to be processed. 233 | * 234 | * @throws FlowException If Transition is not allowed. 235 | * 236 | * @return void 237 | */ 238 | private function guardAllowedTransition(?string $transitionName): void 239 | { 240 | if (!$this->isWorkflowStarted()) { 241 | if ($transitionName === null || $transitionName === $this->getWorkflow()->getStartTransition()->getName()) { 242 | return; 243 | } 244 | 245 | throw new FlowException( 246 | sprintf( 247 | 'Not allowed to process transition "%s". Workflow "%s" not started for item "%s"', 248 | $transitionName, 249 | $this->workflow->getName(), 250 | $this->item->getEntityId() 251 | ) 252 | ); 253 | } 254 | 255 | $step = $this->getCurrentStep(); 256 | 257 | if (!$step->isTransitionAllowed($transitionName)) { 258 | throw new FlowException( 259 | sprintf( 260 | 'Not allowed to process transition "%s". Transition is not allowed in step "%s"', 261 | $transitionName, 262 | $step->getName() 263 | ) 264 | ); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/Handler/RepositoryBasedTransitionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Handler; 16 | 17 | use Netzmacht\Workflow\Data\EntityRepository; 18 | use Netzmacht\Workflow\Data\StateRepository; 19 | use Netzmacht\Workflow\Exception\WorkflowException; 20 | use Netzmacht\Workflow\Flow\Item; 21 | use Netzmacht\Workflow\Flow\State; 22 | use Netzmacht\Workflow\Flow\Workflow; 23 | use Netzmacht\Workflow\Transaction\TransactionHandler; 24 | 25 | /** 26 | * Class RepositoryBasedTransitionHandler handles the transition to another step in the workflow. 27 | * 28 | * It uses an collection repository approach to store entities. 29 | * 30 | * @package Netzmacht\Workflow 31 | */ 32 | class RepositoryBasedTransitionHandler extends AbstractTransitionHandler 33 | { 34 | /** 35 | * The entity repository. 36 | * 37 | * @var EntityRepository 38 | */ 39 | private $entityRepository; 40 | 41 | /** 42 | * The state repository. 43 | * 44 | * @var StateRepository 45 | */ 46 | private $stateRepository; 47 | 48 | /** 49 | * Construct. 50 | * 51 | * @param Item $item The item. 52 | * @param Workflow $workflow The current workflow. 53 | * @param string|null $transitionName The transition to be handled. 54 | * @param EntityRepository $entityRepository EntityRepository which stores changes. 55 | * @param StateRepository $stateRepository StateRepository which stores new states. 56 | * @param TransactionHandler $transactionHandler TransactionHandler take care of transactions. 57 | * 58 | * @throws WorkflowException If invalid transition name is given. 59 | */ 60 | public function __construct( 61 | Item $item, 62 | Workflow $workflow, 63 | string $transitionName = null, 64 | EntityRepository $entityRepository, 65 | StateRepository $stateRepository, 66 | TransactionHandler $transactionHandler 67 | ) { 68 | parent::__construct($item, $workflow, $transitionName, $transactionHandler); 69 | 70 | $this->entityRepository = $entityRepository; 71 | $this->stateRepository = $stateRepository; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | * 77 | * @throws \Exception If something went wrong during action execution. 78 | */ 79 | public function transit(): State 80 | { 81 | $this->guardValidated(); 82 | 83 | $this->transactionHandler->begin(); 84 | 85 | try { 86 | $state = $this->executeTransition(); 87 | 88 | foreach ($this->getItem()->releaseRecordedStateChanges() as $state) { 89 | $this->stateRepository->add($state); 90 | } 91 | 92 | $this->entityRepository->add($this->getItem()->getEntity()); 93 | } catch (\Exception $e) { 94 | $this->transactionHandler->rollback(); 95 | 96 | throw $e; 97 | } 98 | 99 | $this->transactionHandler->commit(); 100 | 101 | return $state; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Handler/RepositoryBasedTransitionHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Handler; 16 | 17 | use Netzmacht\Workflow\Data\EntityManager; 18 | use Netzmacht\Workflow\Data\StateRepository; 19 | use Netzmacht\Workflow\Flow\Item; 20 | use Netzmacht\Workflow\Flow\Workflow; 21 | use Netzmacht\Workflow\Transaction\TransactionHandler; 22 | 23 | /** 24 | * Class RepositoryBasedTransitionHandlerFactory creates a repository based transition handler. 25 | * 26 | * @package Netzmacht\Workflow\Factory 27 | */ 28 | class RepositoryBasedTransitionHandlerFactory implements TransitionHandlerFactory 29 | { 30 | /** 31 | * Transaction handler being used during workflow transitions. 32 | * 33 | * @var TransactionHandler 34 | */ 35 | private $transactionHandler; 36 | 37 | /** 38 | * The entity manager. 39 | * 40 | * @var EntityManager 41 | */ 42 | private $entityManager; 43 | 44 | /** 45 | * Construct. 46 | * 47 | * @param EntityManager $entityManager The entity manager. 48 | * @param TransactionHandler $transactionHandler Transaction handler being used during workflow transitions. 49 | */ 50 | public function __construct( 51 | EntityManager $entityManager, 52 | TransactionHandler $transactionHandler 53 | ) { 54 | $this->transactionHandler = $transactionHandler; 55 | $this->entityManager = $entityManager; 56 | } 57 | 58 | /** 59 | * Create a transition handler. 60 | * 61 | * @param Item $item Workflow item. 62 | * @param Workflow $workflow Workflow definition. 63 | * @param string|null $transitionName Transition name. 64 | * @param string $providerName Provider name. 65 | * @param StateRepository $stateRepository The state repository. 66 | * 67 | * @return TransitionHandler 68 | */ 69 | public function createTransitionHandler( 70 | Item $item, 71 | Workflow $workflow, 72 | ?string $transitionName, 73 | string $providerName, 74 | StateRepository $stateRepository 75 | ): TransitionHandler { 76 | return new RepositoryBasedTransitionHandler( 77 | $item, 78 | $workflow, 79 | $transitionName, 80 | $this->entityManager->getRepository($providerName), 81 | $stateRepository, 82 | $this->transactionHandler 83 | ); 84 | } 85 | 86 | /** 87 | * Get the entity manager. 88 | * 89 | * @return EntityManager 90 | */ 91 | public function getEntityManager(): EntityManager 92 | { 93 | return $this->entityManager; 94 | } 95 | /** 96 | * Get the transaction handler. 97 | * 98 | * @return TransactionHandler 99 | */ 100 | public function getTransactionHandler(): TransactionHandler 101 | { 102 | return $this->transactionHandler; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Handler/TransitionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Handler; 16 | 17 | use Netzmacht\Workflow\Exception\WorkflowException; 18 | use Netzmacht\Workflow\Flow\Context; 19 | use Netzmacht\Workflow\Flow\Exception\TransitionNotFound; 20 | use Netzmacht\Workflow\Flow\Item; 21 | use Netzmacht\Workflow\Flow\State; 22 | use Netzmacht\Workflow\Flow\Step; 23 | use Netzmacht\Workflow\Flow\Transition; 24 | use Netzmacht\Workflow\Flow\Workflow; 25 | 26 | /** 27 | * Class TransitionHandler handles the transition to another step in the workflow. 28 | * 29 | * @package Netzmacht\Workflow 30 | */ 31 | interface TransitionHandler 32 | { 33 | /** 34 | * Get the workflow. 35 | * 36 | * @return Workflow 37 | */ 38 | public function getWorkflow(): Workflow; 39 | 40 | /** 41 | * Get the item. 42 | * 43 | * @return Item 44 | */ 45 | public function getItem(): Item; 46 | 47 | /** 48 | * Get the transition. 49 | * 50 | * @return Transition 51 | * 52 | * @throws TransitionNotFound If transition was not found. 53 | */ 54 | public function getTransition(): Transition; 55 | 56 | /** 57 | * Get current step. Will return null if workflow is not started yet. 58 | * 59 | * @return Step|null 60 | */ 61 | public function getCurrentStep():? Step; 62 | 63 | /** 64 | * Consider if it handles a start transition. 65 | * 66 | * @return bool 67 | */ 68 | public function isWorkflowStarted(): bool; 69 | 70 | /** 71 | * Consider if input is required. 72 | * 73 | * @return array 74 | */ 75 | public function getRequiredPayloadProperties(): array; 76 | 77 | /** 78 | * Consider if transition is available. 79 | * 80 | * @return bool 81 | */ 82 | public function isAvailable(): bool; 83 | 84 | /** 85 | * Get the context. 86 | * 87 | * @return Context 88 | */ 89 | public function getContext(): Context; 90 | 91 | /** 92 | * Validate the input. 93 | * 94 | * @param array $payload The payload. 95 | * 96 | * @return bool 97 | */ 98 | public function validate(array $payload = []): bool; 99 | 100 | /** 101 | * Transit to next step. 102 | * 103 | * @throws WorkflowException For a workflow specific error. 104 | * @throws \Exception For any error caused maybe by 3rd party code in the actions. 105 | * 106 | * @return State 107 | */ 108 | public function transit(): State; 109 | } 110 | -------------------------------------------------------------------------------- /src/Handler/TransitionHandlerFactory.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Handler; 16 | 17 | use Netzmacht\Workflow\Data\StateRepository; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Workflow; 20 | 21 | /** 22 | * Interface TransitionHandlerFactory describes factory for the workflow transition handler. 23 | * 24 | * @package Netzmacht\Workflow\Factory 25 | */ 26 | interface TransitionHandlerFactory 27 | { 28 | /** 29 | * Create a transition handler. 30 | * 31 | * @param Item $item Workflow item. 32 | * @param Workflow $workflow Workflow definition. 33 | * @param string|null $transitionName Transition name. 34 | * @param string $providerName Provider name. 35 | * @param StateRepository $stateRepository The state repository. 36 | * 37 | * @return TransitionHandler 38 | */ 39 | public function createTransitionHandler( 40 | Item $item, 41 | Workflow $workflow, 42 | ?string $transitionName, 43 | string $providerName, 44 | StateRepository $stateRepository 45 | ): TransitionHandler; 46 | } 47 | -------------------------------------------------------------------------------- /src/Manager/CachedManager.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Manager; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Flow\Item; 19 | use Netzmacht\Workflow\Flow\Workflow; 20 | use Netzmacht\Workflow\Handler\TransitionHandler; 21 | 22 | /** 23 | * Workflow manager decorator caching the items and the relation between workflows and entities. 24 | * 25 | * @package Netzmacht\Workflow\Manager 26 | */ 27 | class CachedManager implements Manager 28 | { 29 | /** 30 | * Workflow manager. 31 | * 32 | * @var Manager 33 | */ 34 | private $manager; 35 | 36 | /** 37 | * Workflow entity mapping. 38 | * 39 | * @var array 40 | */ 41 | private $workflows = array(); 42 | 43 | /** 44 | * Cached workflow items. 45 | * 46 | * @var array 47 | */ 48 | private $items = array(); 49 | 50 | /** 51 | * Construct. 52 | * 53 | * @param Manager $manager The inside workflow manager. 54 | */ 55 | public function __construct(Manager $manager) 56 | { 57 | $this->manager = $manager; 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function handle(Item $item, ?string $transitionName = null, bool $changeWorkflow = false): ?TransitionHandler 64 | { 65 | return $this->manager->handle($item, $transitionName); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | public function addWorkflow(Workflow $workflow): Manager 72 | { 73 | $this->manager->addWorkflow($workflow); 74 | 75 | return $this; 76 | } 77 | 78 | /** 79 | * {@inheritdoc} 80 | */ 81 | public function getWorkflow(EntityId $entityId, $entity): Workflow 82 | { 83 | $key = (string) $entityId; 84 | 85 | if (!isset($this->workflows[$key])) { 86 | $this->workflows[$key] = $this->manager->getWorkflow($entityId, $entity); 87 | } 88 | 89 | return $this->workflows[$key]; 90 | } 91 | 92 | /** 93 | * {@inheritdoc} 94 | */ 95 | public function getWorkflowByName(string $name): Workflow 96 | { 97 | return $this->manager->getWorkflowByName($name); 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function getWorkflowByItem(Item $item): Workflow 104 | { 105 | return $this->getWorkflow($item->getEntityId(), $item->getEntity()); 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function hasWorkflow(EntityId $entityId, $entity): bool 112 | { 113 | $key = (string) $entityId; 114 | 115 | if (isset($this->workflows[$key])) { 116 | return true; 117 | } 118 | 119 | return $this->manager->hasWorkflow($entityId, $entity); 120 | } 121 | 122 | /** 123 | * {@inheritdoc} 124 | */ 125 | public function getWorkflows(): iterable 126 | { 127 | return $this->manager->getWorkflows(); 128 | } 129 | 130 | /** 131 | * {@inheritdoc} 132 | */ 133 | public function createItem(EntityId $entityId, $entity): Item 134 | { 135 | $key = (string) $entityId; 136 | 137 | if (!isset($this->items[$key])) { 138 | $this->items[$key] = $this->manager->createItem($entityId, $entity); 139 | } 140 | 141 | return $this->items[$key]; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Manager/Manager.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Manager; 16 | 17 | use Netzmacht\Workflow\Data\EntityId; 18 | use Netzmacht\Workflow\Exception\WorkflowException; 19 | use Netzmacht\Workflow\Exception\WorkflowNotFound; 20 | use Netzmacht\Workflow\Flow\Item; 21 | use Netzmacht\Workflow\Flow\Workflow; 22 | use Netzmacht\Workflow\Handler\TransitionHandler; 23 | 24 | /** 25 | * Class Manager handles a set of workflows. 26 | * 27 | * Usually there will a different workflow manager for different workflow types. The manager is the API entry point 28 | * when using the workflow API. 29 | * 30 | * @package Netzmacht\Workflow 31 | */ 32 | interface Manager 33 | { 34 | /** 35 | * Handle a workflow transition of an entity will createRepository a transition handler. 36 | * 37 | * If no matching workflow definition is found null will be returned. 38 | * 39 | * @param Item $item The current workflow item. 40 | * @param string $transitionName Transition name, required if workflow has already started. 41 | * @param bool $changeWorkflow If true the item is detached from current workflow if another workflow is used. 42 | * 43 | * @throws WorkflowException If something went wrong. 44 | * 45 | * @return TransitionHandler 46 | */ 47 | public function handle(Item $item, string $transitionName = null, bool $changeWorkflow = false): ?TransitionHandler; 48 | 49 | /** 50 | * Add a workflow to the manager. 51 | * 52 | * @param Workflow $workflow The workflow being added. 53 | * 54 | * @return $this 55 | */ 56 | public function addWorkflow(Workflow $workflow): self; 57 | 58 | /** 59 | * Get a workflow for the given entity. 60 | * 61 | * @param EntityId $entityId The entity id. 62 | * @param mixed $entity The entity. 63 | * 64 | * @return Workflow 65 | * 66 | * @throws WorkflowNotFound When no workflow is found. 67 | */ 68 | public function getWorkflow(EntityId $entityId, $entity): Workflow; 69 | 70 | /** 71 | * Get Workflow by its name. 72 | * 73 | * @param string $name Name of workflow. 74 | * 75 | * @return Workflow 76 | * 77 | * @throws WorkflowNotFound When no workflow is found. 78 | */ 79 | public function getWorkflowByName(string $name): Workflow; 80 | 81 | /** 82 | * Get workflow by item. 83 | * 84 | * @param Item $item Workflow item. 85 | * 86 | * @return Workflow 87 | * 88 | * @throws WorkflowNotFound When no workflow is found. 89 | */ 90 | public function getWorkflowByItem(Item $item): Workflow; 91 | 92 | /** 93 | * Consider if entity has an workflow. 94 | * 95 | * @param EntityId $entityId The entity id. 96 | * @param mixed $entity The entity. 97 | * 98 | * @return bool 99 | */ 100 | public function hasWorkflow(EntityId $entityId, $entity): bool; 101 | 102 | /** 103 | * Get all registered workflows. 104 | * 105 | * @return Workflow[]|iterable 106 | */ 107 | public function getWorkflows(): iterable; 108 | 109 | /** 110 | * Create the item for an entity. 111 | * 112 | * @param EntityId $entityId The entity id. 113 | * @param mixed $entity Current entity. 114 | * 115 | * @return Item 116 | */ 117 | public function createItem(EntityId $entityId, $entity): Item; 118 | } 119 | -------------------------------------------------------------------------------- /src/Manager/WorkflowManager.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Manager; 16 | 17 | use Assert\Assertion; 18 | use Netzmacht\Workflow\Data\EntityId; 19 | use Netzmacht\Workflow\Data\StateRepository; 20 | use Netzmacht\Workflow\Exception\WorkflowNotFound; 21 | use Netzmacht\Workflow\Flow\Exception\FlowException; 22 | use Netzmacht\Workflow\Flow\Item; 23 | use Netzmacht\Workflow\Flow\Workflow; 24 | use Netzmacht\Workflow\Handler\TransitionHandler; 25 | use Netzmacht\Workflow\Handler\TransitionHandlerFactory; 26 | 27 | /** 28 | * Class Manager handles a set of workflows. 29 | * 30 | * Usually there will a different workflow manager for different workflow types. The manager is the API entry point 31 | * when using the workflow API. 32 | * 33 | * @package Netzmacht\Workflow 34 | */ 35 | class WorkflowManager implements Manager 36 | { 37 | /** 38 | * The state repository. 39 | * 40 | * @var StateRepository 41 | */ 42 | private $stateRepository; 43 | 44 | /** 45 | * A set of workflows. 46 | * 47 | * @var Workflow[] 48 | */ 49 | private $workflows; 50 | 51 | /** 52 | * A Transition handler factory. 53 | * 54 | * @var TransitionHandlerFactory 55 | */ 56 | private $handlerFactory; 57 | 58 | /** 59 | * Construct. 60 | * 61 | * @param TransitionHandlerFactory $handlerFactory The transition handler factory. 62 | * @param StateRepository $stateRepository The state repository. 63 | * @param Workflow[] $workflows The set of managed workflows. 64 | */ 65 | public function __construct( 66 | TransitionHandlerFactory $handlerFactory, 67 | StateRepository $stateRepository, 68 | $workflows = [] 69 | ) { 70 | Assertion::allIsInstanceOf($workflows, Workflow::class); 71 | 72 | $this->workflows = $workflows; 73 | $this->handlerFactory = $handlerFactory; 74 | $this->stateRepository = $stateRepository; 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function handle(Item $item, string $transitionName = null, bool $changeWorkflow = false): ?TransitionHandler 81 | { 82 | $entity = $item->getEntity(); 83 | 84 | if (!$this->hasWorkflow($item->getEntityId(), $entity)) { 85 | return null; 86 | } 87 | 88 | $workflow = $this->getWorkflowByItem($item); 89 | 90 | if ($this->hasWorkflowChanged($item, $workflow, !$changeWorkflow) && $changeWorkflow) { 91 | $item->detach(); 92 | } 93 | 94 | $handler = $this->handlerFactory->createTransitionHandler( 95 | $item, 96 | $workflow, 97 | $transitionName, 98 | $item->getEntityId()->getProviderName(), 99 | $this->stateRepository 100 | ); 101 | 102 | return $handler; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public function addWorkflow(Workflow $workflow): Manager 109 | { 110 | $this->workflows[] = $workflow; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * {@inheritdoc} 117 | * 118 | * @throws WorkflowNotFound When no supporting workflow is found. 119 | */ 120 | public function getWorkflow(EntityId $entityId, $entity): Workflow 121 | { 122 | foreach ($this->workflows as $workflow) { 123 | if ($workflow->supports($entityId, $entity)) { 124 | return $workflow; 125 | } 126 | } 127 | 128 | throw WorkflowNotFound::forEntity($entityId); 129 | } 130 | 131 | /** 132 | * {@inheritdoc} 133 | * 134 | * @throws WorkflowNotFound When no workflow with name is found. 135 | */ 136 | public function getWorkflowByName(string $name): Workflow 137 | { 138 | foreach ($this->workflows as $workflow) { 139 | if ($workflow->getName() === $name) { 140 | return $workflow; 141 | } 142 | } 143 | 144 | throw WorkflowNotFound::withName($name); 145 | } 146 | 147 | /** 148 | * {@inheritdoc} 149 | */ 150 | public function getWorkflowByItem(Item $item): Workflow 151 | { 152 | if ($item->getWorkflowName()) { 153 | $workflow = $this->getWorkflowByName($item->getWorkflowName()); 154 | 155 | if ($workflow->supports($item->getEntityId(), $item->getEntity())) { 156 | return $workflow; 157 | } 158 | } 159 | 160 | return $this->getWorkflow($item->getEntityId(), $item->getEntity()); 161 | } 162 | 163 | /** 164 | * {@inheritdoc} 165 | */ 166 | public function hasWorkflow(EntityId $entityId, $entity): bool 167 | { 168 | foreach ($this->workflows as $workflow) { 169 | if ($workflow->supports($entityId, $entity)) { 170 | return true; 171 | } 172 | } 173 | 174 | return false; 175 | } 176 | 177 | /** 178 | * {@inheritdoc} 179 | */ 180 | public function getWorkflows(): iterable 181 | { 182 | return $this->workflows; 183 | } 184 | 185 | /** 186 | * {@inheritdoc} 187 | */ 188 | public function createItem(EntityId $entityId, $entity): Item 189 | { 190 | $stateHistory = $this->stateRepository->find($entityId); 191 | 192 | return Item::reconstitute($entityId, $entity, $stateHistory); 193 | } 194 | 195 | /** 196 | * Guard that already started workflow is the same which is tried to be ran now. 197 | * 198 | * @param Item $item Current workflow item. 199 | * @param Workflow $workflow Selected workflow. 200 | * @param bool $throw If true an error is thrown. 201 | * 202 | * @throws FlowException If item workflow is not the same as current workflow. 203 | * 204 | * @return bool 205 | */ 206 | private function hasWorkflowChanged(Item $item, Workflow $workflow, bool $throw = true): bool 207 | { 208 | if ($item->isWorkflowStarted() && $item->getWorkflowName() != $workflow->getName()) { 209 | $message = sprintf( 210 | 'Item "%s" already process workflow "%s" and cannot be handled by "%s"', 211 | $item->getEntityId(), 212 | $item->getWorkflowName(), 213 | $workflow->getName() 214 | ); 215 | 216 | if ($throw) { 217 | throw new FlowException($message); 218 | } 219 | 220 | return true; 221 | } 222 | 223 | return false; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/Transaction/DelegatingTransactionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Transaction; 16 | 17 | /** 18 | * Class DelegatingTransactionHandler delegates transaction commands to its children handlers. 19 | * 20 | * @package Netzmacht\Workflow\Transaction 21 | */ 22 | class DelegatingTransactionHandler implements TransactionHandler 23 | { 24 | /** 25 | * Transaction handler. 26 | * 27 | * @var TransactionHandler[] 28 | */ 29 | private $transactionHandlers; 30 | 31 | /** 32 | * DelegatingTransactionHandler constructor. 33 | * 34 | * @param TransactionHandler[] $transactionHandlers Child transaction handlers. 35 | */ 36 | public function __construct(array $transactionHandlers) 37 | { 38 | $this->transactionHandlers = $transactionHandlers; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function begin(): void 45 | { 46 | foreach ($this->transactionHandlers as $handler) { 47 | $handler->begin(); 48 | } 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function commit(): void 55 | { 56 | foreach ($this->transactionHandlers as $handler) { 57 | $handler->commit(); 58 | } 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function rollback(): void 65 | { 66 | foreach ($this->transactionHandlers as $handler) { 67 | $handler->rollback(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Transaction/TransactionHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Transaction; 16 | 17 | /** 18 | * Interface TransactionHandler describes the commonly used transaction steps begin, commit and rollback. 19 | * 20 | * @package Netzmacht\Workflow\Transaction 21 | */ 22 | interface TransactionHandler 23 | { 24 | /** 25 | * Begin a transaction. 26 | * 27 | * @return void 28 | */ 29 | public function begin(): void; 30 | 31 | /** 32 | * Commit changes. 33 | * 34 | * @return void 35 | */ 36 | public function commit(): void; 37 | 38 | /** 39 | * Rollback the transaction. 40 | * 41 | * @return void 42 | */ 43 | public function rollback(): void; 44 | } 45 | -------------------------------------------------------------------------------- /src/Util/Comparison.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2014-2017 netzmacht David Molineus 9 | * @license LGPL 3.0 https://github.com/netzmacht/workflow 10 | * @filesource 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | namespace Netzmacht\Workflow\Util; 16 | 17 | /** 18 | * Class Comparison is an util class to allow value comparison by passing two values. 19 | * 20 | * @package Netzmacht\Workflow\Util 21 | */ 22 | final class Comparison 23 | { 24 | const EQUALS = '=='; 25 | const IDENTICAL = '==='; 26 | const NOT_EQUALS = '!='; 27 | const NOT_IDENTICAL = '!=='; 28 | const GREATER_THAN = '>'; 29 | const LESSER_THAN = '<'; 30 | const LESSER_THAN_OR_EQUALS = '<='; 31 | const GREATER_THAN_OR_EQUALS = '>='; 32 | 33 | /** 34 | * Operation method mapping cache. 35 | * 36 | * @var array 37 | */ 38 | private static $operators; 39 | 40 | /** 41 | * Compare two values. 42 | * 43 | * @param mixed $valueA Value a. 44 | * @param mixed $valueB Value b. 45 | * @param string $operator The operator for the comparison. 46 | * 47 | * @return bool 48 | */ 49 | public static function compare($valueA, $valueB, string $operator): bool 50 | { 51 | $method = self::getOperatorMethod($operator); 52 | 53 | if ($method) { 54 | return call_user_func([get_called_class(), $method], $valueA, $valueB); 55 | } 56 | 57 | return false; 58 | } 59 | 60 | /** 61 | * Consider if two values equals. 62 | * 63 | * @param mixed $valueA Value a. 64 | * @param mixed $valueB Value b. 65 | * 66 | * @return bool 67 | */ 68 | public static function equals($valueA, $valueB): bool 69 | { 70 | return $valueA == $valueB; 71 | } 72 | 73 | /** 74 | * Consider if both values are identical. 75 | * 76 | * It uses the === operator of php. 77 | * 78 | * @param mixed $valueA Value a. 79 | * @param mixed $valueB Value b. 80 | * 81 | * @return bool 82 | */ 83 | public static function identical($valueA, $valueB): bool 84 | { 85 | return $valueA === $valueB; 86 | } 87 | 88 | /** 89 | * Consider if two values not equals. 90 | * 91 | * @param mixed $valueA Value a. 92 | * @param mixed $valueB Value b. 93 | * 94 | * @return bool 95 | */ 96 | public static function notEquals($valueA, $valueB): bool 97 | { 98 | return !static::equals($valueA, $valueB); 99 | } 100 | 101 | /** 102 | * Consider if two values are not identical. 103 | * 104 | * @param mixed $valueA Value a. 105 | * @param mixed $valueB Value b. 106 | * 107 | * @return bool 108 | */ 109 | public static function notIdentical($valueA, $valueB): bool 110 | { 111 | return !static::identical($valueA, $valueB); 112 | } 113 | 114 | /** 115 | * Consider if value a is greater than value b. 116 | * 117 | * @param mixed $valueA Value a. 118 | * @param mixed $valueB Value b. 119 | * 120 | * @return bool 121 | */ 122 | public static function greaterThan($valueA, $valueB): bool 123 | { 124 | return $valueA > $valueB; 125 | } 126 | 127 | /** 128 | * Consider if value a is greater than or equals value b. 129 | * 130 | * @param mixed $valueA Value a. 131 | * @param mixed $valueB Value b. 132 | * 133 | * @return bool 134 | */ 135 | public static function greaterThanOrEquals($valueA, $valueB): bool 136 | { 137 | return $valueA >= $valueB; 138 | } 139 | 140 | /** 141 | * Consider if value a is lesser than value b. 142 | * 143 | * @param mixed $valueA Value a. 144 | * @param mixed $valueB Value b. 145 | * 146 | * @return bool 147 | */ 148 | public static function lesserThan($valueA, $valueB): bool 149 | { 150 | return $valueA < $valueB; 151 | } 152 | 153 | /** 154 | * Consider if value a is lesser than or equals value b. 155 | * 156 | * @param mixed $valueA Value a. 157 | * @param mixed $valueB Value b. 158 | * 159 | * @return bool 160 | */ 161 | public static function lesserThanOrEquals($valueA, $valueB): bool 162 | { 163 | return $valueA <= $valueB; 164 | } 165 | 166 | /** 167 | * Get operator method. Returns false if metod not set. 168 | * 169 | * @param string $operator The current operator. 170 | * 171 | * @return string|bool 172 | */ 173 | private static function getOperatorMethod(string $operator) 174 | { 175 | $operators = self::getOperators(); 176 | 177 | if (isset($operators[$operator])) { 178 | return $operators[$operator]; 179 | } 180 | 181 | return false; 182 | } 183 | 184 | /** 185 | * Get operator method mapping. 186 | * 187 | * @return array 188 | */ 189 | private static function getOperators(): array 190 | { 191 | if (!is_array(self::$operators)) { 192 | $reflector = new \ReflectionClass(get_called_class()); 193 | $constants = $reflector->getConstants(); 194 | $operators = array(); 195 | 196 | foreach ($constants as $name => $operator) { 197 | $parts = explode('_', $name); 198 | $parts = array_map( 199 | function ($item) { 200 | $item = strtolower($item); 201 | $item = ucfirst($item); 202 | 203 | return $item; 204 | }, 205 | $parts 206 | ); 207 | 208 | $operators[$operator] = lcfirst(implode('', $parts)); 209 | } 210 | 211 | self::$operators = $operators; 212 | } 213 | 214 | return self::$operators; 215 | } 216 | } 217 | --------------------------------------------------------------------------------