-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 7490167
Showing
29 changed files
with
8,017 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 4 | ||
trim_trailing_whitespace = true | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false | ||
|
||
[*.{yml,yaml}] | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
name: CI | ||
|
||
on: | ||
push: | ||
|
||
jobs: | ||
php-tests: | ||
runs-on: ubuntu-latest | ||
|
||
strategy: | ||
matrix: | ||
php: [7.4] | ||
|
||
name: PHP${{ matrix.php }} | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v1 | ||
|
||
- name: Setup PHP | ||
uses: shivammathur/setup-php@v1 | ||
with: | ||
php-version: ${{ matrix.php }} | ||
extensions: json | ||
|
||
- name: Install dependencies | ||
run: composer update --prefer-dist --no-suggest --no-interaction --no-scripts | ||
|
||
- name: Check codestyle | ||
run: vendor/bin/ecs check --config=dev/easy-coding-standard.yml --no-progress-bar . | ||
|
||
- name: Execute unit tests | ||
run: vendor/bin/phpunit --testdox --colors=always --coverage-text=report/coverage.txt | ||
|
||
- name: Archive code coverage results | ||
if: ${{ github.event_name != 'pull_request' }} | ||
uses: actions/upload-artifact@v1 | ||
with: | ||
name: code-coverage-report | ||
path: report/coverage.txt | ||
|
||
- name: Report coverage in PR | ||
if: ${{ github.event_name == 'pull_request' }} | ||
uses: slavcodev/[email protected] | ||
with: | ||
github_token: ${{ secrets.GITHUB_TOKEN }} | ||
clover_file: "logs/clover.xml" | ||
threshold_alert: 10 | ||
threshold_warning: 50 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/node_modules | ||
/report | ||
/vendor | ||
.env | ||
.env.backup | ||
.phpunit.result.cache | ||
npm-debug.log | ||
yarn-error.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Default to showing help section | ||
info: intro help | ||
|
||
intro: | ||
@echo "" | ||
@echo "Statinator" | ||
@echo "" | ||
|
||
# =========================== | ||
# Main commands | ||
# =========================== | ||
|
||
# Dependencies | ||
install: intro do-composer-install do-assets-install | ||
update: intro do-composer-update | ||
|
||
# Tests | ||
tests: intro do-test-unit do-test-report | ||
test-unit: intro do-test-unit | ||
|
||
# Development | ||
pre-commit: intro do-lint-staged-files do-commit-intro | ||
codestyle: intro do-cs-ecs | ||
codestyle-fix: intro do-cs-ecs-fix | ||
|
||
# =========================== | ||
# Overview of commands | ||
# =========================== | ||
|
||
help: | ||
@echo "\n=== Make commands ===\n" | ||
@echo "Dependencies" | ||
@echo " make install Make the project ready for development." | ||
@echo " make update Update backend and frontend dependencies." | ||
@echo " make reset Reinstall backend and frontend dependencies." | ||
@echo "\nTests" | ||
@echo " make tests Run tests." | ||
@echo " make test-unit Run unit tests." | ||
@echo "\nDevelopment" | ||
@echo " make codestyle Check if the codestyle is OK." | ||
@echo " make codestyle-fix Check and fix your messy codestyle." | ||
|
||
# =========================== | ||
# Recipes | ||
# =========================== | ||
|
||
# Dependencies | ||
do-composer-install: | ||
@echo "\n=== Installing composer dependencies ===\n"\ | ||
COMPOSER_MEMORY_LIMIT=-1 composer install | ||
|
||
do-composer-update: | ||
@echo "\n=== Updating composer dependencies ===\n"\ | ||
COMPOSER_MEMORY_LIMIT=-1 composer update | ||
|
||
# Development | ||
do-commit-intro: | ||
@echo "\n=== Let's ship it! ===\n" | ||
|
||
do-lint-staged-files: | ||
@node_modules/.bin/lint-staged | ||
|
||
do-cs-ecs: | ||
./vendor/bin/ecs check --config=dev/easy-coding-standard.yml | ||
|
||
do-cs-ecs-fix: | ||
./vendor/bin/ecs check --fix --config=dev/easy-coding-standard.yml | ||
|
||
# Project | ||
do-assets-install: | ||
@echo "\n=== Installing npm dependencies ===\n" | ||
npm install | ||
|
||
# Tests | ||
do-test-unit: | ||
@echo "\n=== Running unit tests ===\n" | ||
vendor/bin/phpunit | ||
|
||
do-test-report: | ||
@echo "\n=== Click the link below to see the test coverage report ===\n" | ||
@echo "report/index.html" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
# Statinator | ||
|
||
[![CI/CD][ico-actions]][link-actions] | ||
|
||
Next-gen PHP state machines and state charts. | ||
|
||
## Installation | ||
|
||
`composer require jeroen-g/statinator` | ||
|
||
## Usage | ||
|
||
Imagine you want to create a state machine for a light switch in which the light can be in the state ON or OFF. | ||
A diagram would look as follows: | ||
|
||
![diagram](docs/lights-diagram.png) | ||
|
||
_Source: [statecharts.github.io](https://statecharts.github.io/)_ | ||
|
||
The Statinator configuration for this state machine would look like this: | ||
|
||
```php | ||
$config = [ | ||
'states' => [ | ||
'LIGHTS_ON', | ||
'LIGHTS_OFF', | ||
], | ||
'transitions' => [ | ||
'FLICK_ON' => [ | ||
'from' => ['LIGHTS_OFF'], | ||
'to' => ['LIGHTS_ON'], | ||
], | ||
'FLICK_OFF' => [ | ||
'from' => ['LIGHTS_ON'], | ||
'to' => ['LIGHTS_OFF'], | ||
], | ||
], | ||
]; | ||
``` | ||
|
||
Then in your application you should instantiate Statinator with this configuration. | ||
|
||
```php | ||
use JeroenG\Statinator\Statinator; | ||
$statinator = new Statinator($config); | ||
``` | ||
|
||
The Statinator accepts a second parameter as a repository implementing the `ActionRepositoryInterface`, | ||
this (optionally) stores successful and failed actions. | ||
|
||
Any object that you want to use with a state machine should implement the `StatableInterface` contract. | ||
For the example of the light switch this might be: | ||
|
||
```php | ||
use JeroenG\Statinator\StatableInterface; | ||
|
||
class LightSwitch implements StatableInterface { | ||
private string $currentState; | ||
|
||
public function __construct(string $initialState) | ||
{ | ||
$this->currentState = $initialState; | ||
} | ||
|
||
public function getState(): string | ||
{ | ||
return $this->currentState; | ||
} | ||
|
||
public function setState(string $to): void | ||
{ | ||
$this->currentState = $to; | ||
} | ||
} | ||
``` | ||
|
||
Anywhere in your code, you may use the Statinator instance to get a state machine and interact with its state and transitions. | ||
|
||
```php | ||
$lightSwitch = new LightSwitch('LIGHTS_OFF'); | ||
$sm = $statinator->get($lightSwitch); | ||
|
||
$sm->getState(); // LIGHTS_OFF | ||
$sm->can('FLICK_ON'); // true | ||
$sm->can('FLICK_OFF'); // false | ||
|
||
$sm->apply('FLICK_ON'); | ||
|
||
$sm->getState(); // LIGHTS_ON | ||
$sm->can('FLICK_ON'); // false | ||
$sm->can('FLICK_OFF'); // true | ||
``` | ||
|
||
While seeing objects move from one state to another is quite cool, the real power lies with the actions that you may define | ||
for transitions. | ||
|
||
Before and after a state changes, the state machine executes any available actions. You can define actions as part of the | ||
global configuration, or by using a setter on the Statinator. | ||
|
||
```php | ||
// Using the configuration | ||
$config = [ | ||
// ... | ||
'transitions' => [ | ||
'FLICK_ON' => [ | ||
'from' => ['LIGHTS_OFF'], | ||
'to' => ['LIGHTS_ON'], | ||
'on' => [ | ||
'entry' => NotifyLightsAreOn::class, | ||
'exit' => NotifyLightsAreOff::class, | ||
], | ||
], | ||
// ... | ||
], | ||
]; | ||
|
||
// Or using a setter | ||
$statinator->onEntry('FLICK_ON', NotifyLightsAreOn::class); | ||
$statinator->onExit('FLICK_ON', NotifyLightsAreOff::class); | ||
``` | ||
|
||
The actions can be a `callable` or a class implementing `ActionableInterface`. | ||
|
||
```php | ||
$statinator->onEntry('FLICK_OFF', fn(StateMachineInterface $stateMachine, string $transition) => var_dump('Called it!')); | ||
```` | ||
|
||
```php | ||
use JeroenG\Statinator\ActionableInterface; | ||
use JeroenG\Statinator\StateMachineInterface; | ||
|
||
class NotifyLightsAreOn implements ActionableInterface { | ||
private StateMachineInterface $stateMachine; | ||
private string $transition; | ||
|
||
public function getState(): string | ||
{ | ||
return $this->stateMachine->getState(); | ||
} | ||
|
||
public function execute(StateMachineInterface $stateMachine, string $transition): void | ||
{ | ||
$this->stateMachine = $stateMachine; | ||
$this->transition = $transition; | ||
|
||
$notifier = new MyNotifier(); | ||
$notifier->send('Lights are turned on'); | ||
} | ||
|
||
public function getTransition(): string | ||
{ | ||
return $this->transition; | ||
} | ||
} | ||
``` | ||
|
||
If your action implements the `ActionableInterface` it could be saved using a repository that is passed when you instantiated Statinator. | ||
This package ships with an `ArrayActionRepository` that is used by default and does not persist the data. Another possibility | ||
is the `LogActionRepository`, this one requires a PSR-compliant logger where the data will be persisted. | ||
You can of course also make your own repository (maybe one that uses the database) as long as it implements the `ActionRepositoryInterface`. | ||
|
||
## Contributing | ||
|
||
The project includes a Makefile to run install it, run the tests and check the code style. | ||
|
||
|
||
[link-actions]: https://github.com/Jeroen-G/alpine-artisan/actions?query=workflow%3ACI%2FCD | ||
[ico-actions]: https://img.shields.io/github/workflow/status/Jeroen-G/alpine-artisan/CI?label=CI%2FCD&style=flat-square |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "jeroen-g/statinator", | ||
"description": "Next-gen PHP state machines", | ||
"type": "library", | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Jeroen", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"php": "^7.4" | ||
}, | ||
"require-dev": { | ||
"phpunit/phpunit": "^9.2", | ||
"symplify/easy-coding-standard": "^8.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"JeroenG\\Statinator\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"JeroenG\\Statinator\\Tests\\": "tests/" | ||
}, | ||
"classmap": ["src/"] | ||
} | ||
} |
Oops, something went wrong.