From 0c85fa94ed1cfca5811a12a36dd39f207d8faea5 Mon Sep 17 00:00:00 2001 From: Christian Sciberras Date: Thu, 22 Jul 2021 09:31:38 +0200 Subject: [PATCH] Small improvements (#2) * Avoid creative use of names potentially break builder * Use anon class instead of additional class file in tests * Fix code coverage filter * Expose builder properties so custom builders can have custom state/transition classes * Small doc update * Remove assumption that repository is traversable * Just use iterators --- README.md | 22 +++++++++------ phpunit.xml.dist | 2 +- src/Implementation/Builder.php | 19 +++++++++---- .../Repositories/AbstractTraversable.php | 3 ++- src/Implementation/Traits/StateTraversion.php | 4 +-- .../TransitionRepositoryInterface.php | 4 +-- test/JiraIssueTest.php | 27 +++++++++++++++++-- test/TestStateAwareItem.php | 26 ------------------ 8 files changed, 60 insertions(+), 47 deletions(-) delete mode 100644 test/TestStateAwareItem.php diff --git a/README.md b/README.md index 0b71bca..3ae14b9 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,23 @@ This library provides some interfaces and a basic implementation of a State Engi **Highlights:** - Highly composable - everything can be replaced as desired - [PSR-14](http://www.php-fig.org/psr/psr-14/) (Event Dispatcher) compatible -- Fluent interface -- Generates PlantUML markup +- Fluent builder interface ([see "From Scratch"](#from-scratch)) +- Generates PlantUML markup ([see "Examples & Testing"](#examples--testing)) ## Installation The recommended and easiest way to install this library is through [Composer](https://getcomposer.org/): ```bash -composer require uuf6429/state-engine-php "~2.0" +composer require uuf6429/state-engine-php "^1.0" ``` ## Why? In principle such an engine is easy to implement, but in practice it is typically implemented badly or forgotten. -For instance, one might have an `is_active` field thinking there will not be other states and then later on realizes the -need for an `is_pending` field, at which point refactoring flags to state is too late. +For instance, one might have an `is_active` field thinking there will not be other states and then later on an +`is_pending` field is needed, at which point refactoring flags to state is too late. In any case, this library abstracts away that situation or at least decreases the amount of code. @@ -47,7 +47,13 @@ There are a few key parts to how this works: ## Usage -Usage depends on if you want to do it from scratch or plug it into your existing code. +You have the possibility to use it from scratch or plug it into your existing. There are basically three parts to it: +1. configuring the engine (creating states and transitions) +2. using the engine (eg, in a web controller or service) +3. (optionally) handling events (with the same event dispatcher provided to the engine) + +A slightly different situation would be when you need to provide a list of valid transitions, for example to the user. +In this case, having the [`StateTraversion`](https://github.com/uuf6429/state-engine-php/blob/main/src/Implementation/Traits/StateTraversion.php) trait on the repository would be useful. ### From Scratch @@ -123,8 +129,8 @@ $doorStateMutator = Builder::stateMutator( ## Examples & Testing -The JiraIssueTest class serves as a test as well as a realistic example of how Jira Issue states could be set up. +The [`JiraIssueTest`](https://github.com/uuf6429/state-engine-php/blob/main/test/JiraIssueTest.php) class serves as a test as well as a realistic example of how Jira Issue states could be set up. -The test also generates the Plant UML diagram below (embedded as an image due to GFM limitations). +The test also generates the PlantUML diagram below (embedded as an image due to GFM limitations): ![example](https://www.planttext.com/api/plantuml/svg/TPBDRiCW48JlFCKUauDV88SgZgfAlLIrymGqJ2rK31PiBENjYurfux_hpZVB370EB3tVMoF4uI9lFyOrHogA5pgKLff7qE589xgWqPRaD5cIxvPUqG_ScmnSi8ygVJjF2ZsCwrfO5a_xHbCDgHuZDNcpJZVNTWQCbUNlr1FLuBktn8w-qb0i5wuwV02AMkSHOx7K9cnR_ikaqhCEMLmqgCg1lyAg8L5Lxe8r36J0nbNvfEmwfqnNTjqyqZn5hf0IfGQCmDes8i-tDrTbZAGDr1xtb3sodpA4WTtG9rzmfeTAZpKg8vsdwmTr7QmGvtY9yJV-0W00) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index e2e1323..85d81d5 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,7 +8,7 @@ - ../src + ./src diff --git a/src/Implementation/Builder.php b/src/Implementation/Builder.php index 675f8c4..ef7665b 100644 --- a/src/Implementation/Builder.php +++ b/src/Implementation/Builder.php @@ -9,21 +9,23 @@ use uuf6429\StateEngine\Implementation\Entities\State; use uuf6429\StateEngine\Implementation\Entities\Transition; use uuf6429\StateEngine\Implementation\Repositories\ArrayRepository; +use uuf6429\StateEngine\Interfaces\EngineInterface; use uuf6429\StateEngine\Interfaces\StateAwareInterface; use uuf6429\StateEngine\Interfaces\StateInterface; use uuf6429\StateEngine\Interfaces\TransitionInterface; +use uuf6429\StateEngine\Interfaces\TransitionRepositoryInterface; class Builder { /** * @var array */ - private array $states = []; + protected array $states = []; /** * @var array */ - private array $transitions = []; + protected array $transitions = []; private function __construct() { @@ -80,7 +82,7 @@ public function addTransition(string $oldStateName, string $newStateName, ?strin throw new BuilderStateNotDeclaredException($newStateName); } - $transitionName = "$oldStateName -> $newStateName"; + $transitionName = "($oldStateName) -> ($newStateName)"; if (isset($this->transitions[$transitionName])) { throw new BuilderTransitionAlreadyDeclaredException($oldStateName, $newStateName); } @@ -94,12 +96,19 @@ public function addTransition(string $oldStateName, string $newStateName, ?strin return $this; } - public function getRepository(): ArrayRepository + /** + * @return ArrayRepository + */ + public function getRepository(): TransitionRepositoryInterface { return new ArrayRepository(array_values($this->transitions)); } - public function getEngine(?EventDispatcherInterface $eventDispatcher = null): Engine + /** + * @param EventDispatcherInterface|null $eventDispatcher + * @return Engine + */ + public function getEngine(?EventDispatcherInterface $eventDispatcher = null): EngineInterface { return new Engine($this->getRepository(), $eventDispatcher); } diff --git a/src/Implementation/Repositories/AbstractTraversable.php b/src/Implementation/Repositories/AbstractTraversable.php index 313c785..9e20cf3 100644 --- a/src/Implementation/Repositories/AbstractTraversable.php +++ b/src/Implementation/Repositories/AbstractTraversable.php @@ -3,6 +3,7 @@ namespace uuf6429\StateEngine\Implementation\Repositories; use IteratorAggregate; +use Traversable; use uuf6429\StateEngine\Implementation\Traits\HasTransition; use uuf6429\StateEngine\Implementation\Traits\StateTraversion; use uuf6429\StateEngine\Interfaces\TransitionRepositoryInterface; @@ -11,7 +12,7 @@ abstract class AbstractTraversable implements TransitionRepositoryInterface, Ite { use HasTransition, StateTraversion; - public function all() + public function all(): Traversable { return $this->getIterator(); } diff --git a/src/Implementation/Traits/StateTraversion.php b/src/Implementation/Traits/StateTraversion.php index 2ccb843..2388006 100644 --- a/src/Implementation/Traits/StateTraversion.php +++ b/src/Implementation/Traits/StateTraversion.php @@ -16,7 +16,7 @@ public function getForwardTransitions(StateInterface $state): array { /** @var $this TransitionRepositoryInterface */ return array_values(array_filter( - iterator_to_array($this), + iterator_to_array($this->all()), static function (TransitionInterface $transition) use ($state): bool { return $transition->getOldState()->equals($state); } @@ -27,7 +27,7 @@ public function getBackwardTransitions(StateInterface $state): array { /** @var $this TransitionRepositoryInterface */ return array_values(array_filter( - iterator_to_array($this), + iterator_to_array($this->all()), static function (TransitionInterface $transition) use ($state): bool { return $transition->getNewState()->equals($state); } diff --git a/src/Interfaces/TransitionRepositoryInterface.php b/src/Interfaces/TransitionRepositoryInterface.php index 92eaac1..d395c43 100644 --- a/src/Interfaces/TransitionRepositoryInterface.php +++ b/src/Interfaces/TransitionRepositoryInterface.php @@ -17,7 +17,7 @@ public function has(TransitionInterface $transition): bool; /** * Returns an array or Traversable object of all transitions. * - * @return array|Traversable|TransitionInterface[] + * @return Traversable|TransitionInterface[] */ - public function all(); + public function all(): Traversable; } diff --git a/test/JiraIssueTest.php b/test/JiraIssueTest.php index 4293e6f..1b34c14 100644 --- a/test/JiraIssueTest.php +++ b/test/JiraIssueTest.php @@ -8,6 +8,7 @@ use uuf6429\StateEngine\Implementation\Entities\State; use uuf6429\StateEngine\Interfaces\EngineInterface; use uuf6429\StateEngine\Interfaces\StateAwareInterface; +use uuf6429\StateEngine\Interfaces\StateInterface; use uuf6429\StateEngine\Interfaces\TransitionInterface; use uuf6429\StateEngine\Interfaces\TransitionRepositoryInterface; @@ -85,7 +86,7 @@ public function test_that_transitioning_from_in_dev_to_ready_for_qa_is_allowed() { $this->expectNotToPerformAssertions(); - $item = new TestStateAwareItem(new State('in-dev')); + $item = $this->buildStatefulItem(new State('in-dev')); $this->engine->changeState($item, new State('ready-for-qa')); } @@ -93,7 +94,7 @@ public function test_that_transitioning_from_in_dev_to_in_qa_is_not_allowed(): v { $this->expectExceptionMessage('Cannot change state from in-dev to in-qa; no such transition was defined.'); - $item = new TestStateAwareItem(new State('in-dev')); + $item = $this->buildStatefulItem(new State('in-dev')); $this->engine->changeState($item, new State('in-qa')); } @@ -142,4 +143,26 @@ public function test_that_the_engine_reads_and_writes_state(): void $this->engine->changeState($mock, $newState); } + + private function buildStatefulItem(State $initialState): StateAwareInterface + { + return new class($initialState) implements StateAwareInterface { + private StateInterface $state; + + public function __construct(StateInterface $initialState) + { + $this->state = $initialState; + } + + public function getState(): StateInterface + { + return $this->state; + } + + public function setState(StateInterface $newState): void + { + $this->state = $newState; + } + }; + } } diff --git a/test/TestStateAwareItem.php b/test/TestStateAwareItem.php deleted file mode 100644 index 91eb38e..0000000 --- a/test/TestStateAwareItem.php +++ /dev/null @@ -1,26 +0,0 @@ -state = $initialState; - } - - public function getState(): StateInterface - { - return $this->state; - } - - public function setState(StateInterface $newState): void - { - $this->state = $newState; - } -}