Skip to content

Commit

Permalink
Support mermaid; modernize codebase (#7)
Browse files Browse the repository at this point in the history
* Support mermaid; modernize codebase

* Improve traits

* Rename methods
  • Loading branch information
uuf6429 authored Sep 7, 2024
1 parent 30297b3 commit 4077637
Show file tree
Hide file tree
Showing 29 changed files with 462 additions and 183 deletions.
10 changes: 10 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
root = true

[*]
end_of_line = lf
charset = utf-8
indent_size = 4
indent_style = space
max_line_length = 120
insert_final_newline = true
trim_trailing_whitespace = true
14 changes: 9 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/phpunit.xml.dist export-ignore
/tests export-ignore
.github export-ignore
tests export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.ph export-ignore
phpstan.dist.neon export-ignore
phpunit.xml.dist export-ignore
README.md export-ignore
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github: [uuf6429]
custom: ['https://paypal.me/uuf6429']
49 changes: 26 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,37 @@ on:

jobs:

build:
name: Test
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- run: composer update --ansi --no-progress --prefer-dist --no-interaction
- run: composer run lint

Test:
strategy:
fail-fast: false
matrix:
php: [ '7.4', '8.0' ]

os: [ 'ubuntu-latest' ]
php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ]
include:
- php: '8.3'
os: 'macos-latest'
- php: '8.3'
os: 'windows-latest'
runs-on: ${{ matrix.os }}
steps:
- name: Set up PHP
uses: shivammathur/setup-php@v2
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: xdebug2

- name: Checkout code
uses: actions/checkout@v2
with:
fetch-depth: 2

- name: Download dependencies
uses: ramsey/composer-install@v1
with:
composer-options: --no-interaction --prefer-dist --optimize-autoloader

- name: Run tests
run: ./vendor/bin/phpunit --coverage-clover coverage.xml

- name: Upload to Codecov
coverage: xdebug
- run: composer update --ansi --no-progress --prefer-dist --no-interaction
- run: composer run test:cover
- uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: bash <(curl -s https://codecov.io/bash)
11 changes: 7 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.idea/
vendor/
*.cache
*.iml
/vendor/
composer.lock
phpstan.neon
.php-cs-fixer.php
.php-cs-fixer.cache
coverage.xml
phpunit.xml
.phpunit.result.cache
14 changes: 14 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRules([
'@PER-CS2.0' => true,
'@PER-CS2.0:risky' => true,
'cast_spaces' => ['space' => 'none'],
])
->setFinder(
(new PhpCsFixer\Finder())
->in(__DIR__),
);
68 changes: 53 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@
This library provides some interfaces and a basic implementation of a State Engine or State Machine.

**Highlights:**

- Dual functionality:
1. Either as a basic state engine; switching to a desired state as long the transition is defined)
([see "JiraIssueTest"](#jiraissuetest-state-engine))
2. Or a more sophisticated state machine; same as above but matching data for any state
([see "TurnstileTest"](#turnstiletest-state-machine))
1. Either as a basic state engine; switching to a desired state as long the transition is
defined ([see "JiraIssueTest"](#jiraissuetest-state-engine))
2. Or a more sophisticated state machine; same as above but matching data for any
state ([see "TurnstileTest"](#turnstiletest-state-machine))
- Highly composable - everything can be replaced as desired
- [PSR-14](http://www.php-fig.org/psr/psr-14/) (Event Dispatcher) compatible
- Fluent builder interface ([see "From Scratch"](#from-scratch))
- Generates PlantUML markup ([see "Examples & Testing"](#examples--testing))
- Generates Mermaid or 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"
```shell
composer require "uuf6429/state-engine-php"
```

## 🧐 Why?
Expand Down Expand Up @@ -54,12 +55,15 @@ There are a few key parts to how this works:
## 🚀 Usage

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.
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 @@ -135,24 +139,58 @@ $doorStateManager->changeState($doorStateMutator, new State('closed'));

## 😎 Examples & Testing

You can find some examples in this readme as well as [the tests](https://github.com/uuf6429/state-engine-php/tree/main/tests), some of which explained below.
You can find some examples in this readme as well
as [the tests](https://github.com/uuf6429/state-engine-php/tree/main/tests), some of which are explained below.

### [`JiraIssueTest`](https://github.com/uuf6429/state-engine-php/blob/main/tests/JiraIssueTest.php) State Engine

This test provides a realistic example of how Jira Issue states could be set up.

The test also generates the PlantUML diagram below (embedded as an image due to GFM limitations), thanks to the [Plantable trait](https://github.com/uuf6429/state-engine-php/blob/main/src/Implementation/Traits/Plantable.php):

![jira issue example](https://www.planttext.com/api/plantuml/svg/TPBDRiCW48JlFCKUauDV88SgZgfAlLIrymGqJ2rK31PiBENjYurfux_hpZVB370EB3tVMoF4uI9lFyOrHogA5pgKLff7qE589xgWqPRaD5cIxvPUqG_ScmnSi8ygVJjF2ZsCwrfO5a_xHbCDgHuZDNcpJZVNTWQCbUNlr1FLuBktn8w-qb0i5wuwV02AMkSHOx7K9cnR_ikaqhCEMLmqgCg1lyAg8L5Lxe8r36J0nbNvfEmwfqnNTjqyqZn5hf0IfGQCmDes8i-tDrTbZAGDr1xtb3sodpA4WTtG9rzmfeTAZpKg8vsdwmTr7QmGvtY9yJV-0W00)
The test also generates the Mermaid diagram below, thanks to
the [Mermaidable trait](https://github.com/uuf6429/state-engine-php/blob/main/src/Implementation/Traits/Mermaidable.php):

```mermaid
stateDiagram
s1_backlog: Backlog
s2_analysis: Analysis
s3_in_dev: In Dev
s4_ready_for_dev: Ready for Dev
s5_ready_for_qa: Ready for QA
s6_ready_for_release: Ready for Release
s7_in_qa: In QA
s8_resolved: Resolved
s1_backlog --> s2_analysis: Begin analysis
s1_backlog --> s3_in_dev: Fast-track for development
s2_analysis --> s4_ready_for_dev: Analysis complete
s2_analysis --> s1_backlog: Return to backlog
s4_ready_for_dev --> s2_analysis: Need more details
s4_ready_for_dev --> s3_in_dev: Begin development
s3_in_dev --> s5_ready_for_qa: Send to QA
s3_in_dev --> s6_ready_for_release: Fast-track for release
s3_in_dev --> s4_ready_for_dev: Stop development
s5_ready_for_qa --> s7_in_qa: Begin testing
s7_in_qa --> s4_ready_for_dev: QA Failed
s7_in_qa --> s6_ready_for_release: QA Passed
s6_ready_for_release --> s8_resolved: Released
s8_resolved --> s1_backlog: Reopen
```

### [`TurnstileTest`](https://github.com/uuf6429/state-engine-php/blob/main/tests/JiraIssueTest.php) State Machine

This test illustrates how a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine) can be used to model a [turnstile gate](https://en.wikipedia.org/wiki/Turnstile).
This test illustrates how a [state machine](https://en.wikipedia.org/wiki/Finite-state_machine) can be used to model
a [turnstile gate](https://en.wikipedia.org/wiki/Turnstile).
As before, here's the generated diagram:

![turnstile example](https://www.planttext.com/api/plantuml/svg/SoWkIImgAStDuUBIyCmjI2mkJapAITLKqDMrKz08W7Ej59ppC_CK2d8IarDJk90amEgGDLef1AGM5UVdAPGdvcGNAvHa5EMNfcTmSJcavgM0h040)
```mermaid
stateDiagram
s1_locked: Impassable
s2_open: Passable
s1_locked --> s2_open: Coin placed
s2_open --> s1_locked: Person walks through
```

Here's how the state machine definition looks like and how it could be used:

Here's how the state machine definition looks like and is used:
```php
use App\Models\Turnstile; // example model that implements StateAwareInterface

Expand Down
33 changes: 28 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{
"name": "uuf6429/state-engine",
"type": "library",
"homepage": "https://github.com/uuf6429/state-engine-php",
"readme": "README.md",
"license": "MIT",
"description": "A library providing interfaces and basic implementation of a State Engine or Machine",
"license": "MIT",
"type": "library",
"keywords": [
"state",
"engine",
Expand All @@ -14,18 +12,24 @@
"workflow",
"uuf6429"
],
"readme": "README.md",
"authors": [
{
"name": "Christian Sciberras",
"email": "[email protected]"
}
],
"homepage": "https://github.com/uuf6429/state-engine-php",
"require": {
"php": "^7.4 || ^8.0",
"psr/event-dispatcher": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5"
"ergebnis/composer-normalize": "^2.42",
"friendsofphp/php-cs-fixer": "^3.53",
"phpstan/phpstan": "^1.11",
"phpunit/phpunit": "^9.5 || ^10",
"roave/security-advisories": "dev-latest"
},
"autoload": {
"psr-4": {
Expand All @@ -36,5 +40,24 @@
"psr-4": {
"uuf6429\\StateEngine\\": "tests/"
}
},
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true
},
"process-timeout": 0
},
"scripts": {
"lint": [
"composer normalize --dry-run",
"composer exec phpstan -- analyse --no-progress",
"composer exec php-cs-fixer -- fix --dry-run --show-progress=none --diff"
],
"lint:fix": [
"composer normalize",
"composer exec php-cs-fixer -- fix --show-progress=dots --diff"
],
"test": "phpunit ./tests/",
"test:cover": "@php -dzend_extension=php_xdebug -dxdebug.mode=coverage vendor/bin/phpunit --coverage-clover coverage.xml ./tests/"
}
}
6 changes: 6 additions & 0 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: 9
treatPhpDocTypesAsCertain: true
paths:
- src
- tests
5 changes: 1 addition & 4 deletions src/Exceptions/InvalidArgumentException.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@

namespace uuf6429\StateEngine\Exceptions;

class InvalidArgumentException extends \InvalidArgumentException
{

}
class InvalidArgumentException extends \InvalidArgumentException {}
5 changes: 1 addition & 4 deletions src/Exceptions/RuntimeException.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,4 @@

namespace uuf6429\StateEngine\Exceptions;

class RuntimeException extends \RuntimeException
{

}
class RuntimeException extends \RuntimeException {}
26 changes: 18 additions & 8 deletions src/Implementation/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ class Builder
*/
protected array $transitions = [];

private function __construct()
{

}
final private function __construct() {}

public static function create(): self
{
Expand All @@ -41,9 +38,19 @@ public static function create(): self
public static function makeStateMutator(callable $getter, callable $setter): StateAwareInterface
{
return new class ($getter, $setter) implements StateAwareInterface {
/**
* @var callable(): StateInterface
*/
private $getter;
/**
* @var callable(StateInterface): void
*/
private $setter;

/**
* @param callable(): StateInterface $getter
* @param callable(StateInterface): void $setter
*/
public function __construct(callable $getter, callable $setter)
{
$this->getter = $getter;
Expand Down Expand Up @@ -105,20 +112,23 @@ public function defTransition(string $oldStateName, string $newStateName, ?strin
new Transition(
$this->states[$oldStateName] ?? new State($oldStateName),
$this->states[$newStateName] ?? new State($newStateName),
$description
)
$description,
),
);
}

/**
* @param array<int|string, mixed> $data
*/
public function defDataTransition(string $oldStateName, array $data, string $newStateName, ?string $description = null): self
{
return $this->addTransition(
new TransitionWithData(
$this->states[$oldStateName] ?? new State($oldStateName),
$data,
$this->states[$newStateName] ?? new State($newStateName),
$description
)
$description,
),
);
}

Expand Down
Loading

0 comments on commit 4077637

Please sign in to comment.