Skip to content

Commit

Permalink
Small improvements (#2)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
uuf6429 authored Jul 22, 2021
1 parent 3aad39f commit 0c85fa9
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 47 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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

Expand Down Expand Up @@ -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)
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</testsuites>
<coverage>
<include>
<directory>../src</directory>
<directory>./src</directory>
</include>
</coverage>
</phpunit>
19 changes: 14 additions & 5 deletions src/Implementation/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, StateInterface>
*/
private array $states = [];
protected array $states = [];

/**
* @var array<string, TransitionInterface>
*/
private array $transitions = [];
protected array $transitions = [];

private function __construct()
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Implementation/Repositories/AbstractTraversable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,7 +12,7 @@ abstract class AbstractTraversable implements TransitionRepositoryInterface, Ite
{
use HasTransition, StateTraversion;

public function all()
public function all(): Traversable
{
return $this->getIterator();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Implementation/Traits/StateTraversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Interfaces/TransitionRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
27 changes: 25 additions & 2 deletions test/JiraIssueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -85,15 +86,15 @@ 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'));
}

public function test_that_transitioning_from_in_dev_to_in_qa_is_not_allowed(): void
{
$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'));
}

Expand Down Expand Up @@ -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;
}
};
}
}
26 changes: 0 additions & 26 deletions test/TestStateAwareItem.php

This file was deleted.

0 comments on commit 0c85fa9

Please sign in to comment.