Skip to content

Commit 02027df

Browse files
committed
decoupled transition schema
1 parent dc689f5 commit 02027df

33 files changed

+898
-709
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# CHANGELOG
22

3+
## dev-decoupled-transition-schema
4+
5+
- [BC break] decoupled transition `Schema` from actual state machine for easier DI of a whole schema
6+
- [BC break] removed `Machine::allowTransition()` and several other transition related methods from `MachineInterface`
7+
and `Machine` implementation
8+
- refactored Exception classes into one hierarchy with one global `SMFException`
9+
- [BC break] `CommonCallbackAssertion` and `DefaultCallbackAssertion` rebuilt to run-time machine object so methods
10+
`setObject()` and `setTransition` are no longer valid
11+
- [BC break] `Transition` class now uses run-time machine object so method `assert()` got additional parameter `$machine`
12+
- [BC break] `Schema::addTransition()` (before `Machine::addTransition()`) now throws `ConfigurationException` instead of
13+
`MachineException`
14+
- added `TransitionInterface::onTransition()` method
15+
- [BC break] `Machine::setMachineState` now throws `ConfigurationException` when `Schema` object not set
16+
- unit tests rewritten
17+
- [BC break] `Transition::assert()` doesn't throw `TransitionException` anymore, false is returned if there are no
18+
assertions defined
19+
320
## v1.0.0
421

522
- `MachineTest`: more detailed tests implemented

README.md

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Maybe state machines are not what you usually do with PHP but when you do... just use this.
44
This simple yet powerful framework will keep your state machines within their desired state
5-
transition cycles. You can also modify their behavior whilst running or just configure them
5+
transition schemas. You can also modify their behavior whilst running or just configure them
66
dynamically and launch. You can define state transition conditions you want based upon
77
anonymous functions or class methods. Fire events on each state transition with your favorite
88
event dispatcher.
@@ -60,23 +60,6 @@ For clarity each state machine should have its own state dictionary defined.
6060
class Petition extends Machine {
6161

6262
protected $votesYes, $votesNo;
63-
64-
public function init() {
65-
$this->setInitState(PetitionEnum::DRAFT());
66-
67-
// defines machine's allowed behavior
68-
$this
69-
// prevents changing state upon assertion when AlwaysFalseAssertion is given
70-
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::SENT(), new AlwaysFalseAssertion())
71-
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::CANCELED(), new AlwaysFalseAssertion())
72-
73-
// when no Assertion is given uses DefaultCallbackAssertion which calls assertXToY methods
74-
->allowTransition(PetitionEnum::SENT(), PetitionEnum::VOTED())
75-
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::ACCEPTED())
76-
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::REJECTED())
77-
;
78-
79-
}
8063

8164
public function send()
8265
{
@@ -124,12 +107,38 @@ For clarity each state machine should have its own state dictionary defined.
124107
}
125108
```
126109

110+
### Machine's transition schema definition
111+
112+
As of version 2 transition schema is decoupled from actual machine so it can be easily injected into any machine object
113+
or changed when is necessary to modify machine's behavior later.
114+
115+
```php
116+
117+
$schema = new Schema();
118+
119+
$schema
120+
->setInitState(PetitionEnum::DRAFT())
121+
// prevents changing state upon assertion when AlwaysFalseAssertion is given
122+
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::SENT(), new AlwaysFalseAssertion())
123+
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::CANCELED(), new AlwaysFalseAssertion())
124+
// when no Assertion is given uses DefaultCallbackAssertion which calls assertXToY methods
125+
->allowTransition(PetitionEnum::SENT(), PetitionEnum::VOTED())
126+
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::ACCEPTED())
127+
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::REJECTED());
128+
129+
```
130+
131+
*Remark: By default (when no assertion object is given as parameter) `Schema::allowTransition()` method attaches `DefaultCallbackAssertion`.*
132+
133+
127134
### Machine in-use
128135

129136
```php
130137

131138
$p = new Petition();
132-
$p->init();
139+
$p
140+
->setSchema($schema)
141+
->init();
133142

134143
$p->run();
135144
// <nothing happens>
@@ -150,9 +159,32 @@ For clarity each state machine should have its own state dictionary defined.
150159

151160
### Transition object
152161

153-
Each transition object should have one or more assertion objects attached.
162+
Defined as change from one state to another. Has two basic functions:
163+
- allows to assert if this exact transition is valid considering defined transition conditions by them via
164+
attached assertions or conditions defined within transition class itself
165+
- allows to run certain code in `Transition::onTransition()` method when actual transition happens; this functionality
166+
doubles that defined within machine for situations when it's required to control it externally from within transition
167+
object. As well as with `Machine::onTransition()` method we can also dispatch events from here or use one common \
168+
transition object for several different machines sharing some functionality.
154169

155-
*Remark: By default (when no assertion object is given as parameter) `Machine::allowTransition()` method attaches `DefaultCallbackAssertion`.*
170+
Example:
171+
```php
172+
173+
class SendToVotedTransition extends Transition {
174+
public function assert(MachineInterface $machine)
175+
{
176+
return $machine->hasVotes() ? true : false;
177+
}
178+
179+
public function onTransition(MachineInterface $machine)
180+
{
181+
$machine->getPetitionStats()->incrementVotedCount();
182+
}
183+
}
184+
185+
```
186+
187+
Each transition object should have one or more assertion objects attached.
156188

157189

158190
### Assertion behaviors
@@ -164,7 +196,7 @@ Results in automatic transition when machine is launched.
164196
Be aware:
165197

166198
```php
167-
$machine
199+
$machine->getSchema()
168200
->allowTransition(MachineEnum::ONE(), MachineEnum::TWO(), new AlwaysTrueAssertion)
169201
->allowTransition(MachineEnum::TWO(), MachineEnum::ONE(), new AlwaysTrueAssertion)
170202

@@ -179,11 +211,11 @@ changed upon `setMachineState()` method call only.
179211

180212
#### DefaultCallbackAssertion
181213

182-
Calls `assertXToY` method on machine object (or other object if specified) and makes transition decision upon its return.
214+
Calls `assertXToY` method on machine object and makes transition decision upon its return.
183215

184216
#### CommonCallbackAssertion
185217

186-
Calls `assertTransition` method on machine object (or other object if specified) and makes transition decision upon its return.
218+
Calls `assertTransition` method on machine object and makes transition decision upon its return.
187219

188220
#### CallbackAssertion
189221

example/example01.php

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
<?php
1+
<?php /** @noinspection ALL */
22

33
namespace Coff\SMF\Example;
44

55
use Coff\SMF\Assertion\AlwaysFalseAssertion;
6+
use Coff\SMF\Schema\Schema;
7+
use Coff\SMF\Exception\ConfigurationException;
8+
use Coff\SMF\Exception\SchemaException;
69
use Coff\SMF\Exception\MachineException;
710
use Coff\SMF\Exception\TransitionException;
811
use Coff\SMF\Machine;
@@ -36,25 +39,10 @@ class Petition extends Machine
3639

3740
protected $votesYes, $votesNo;
3841

39-
public function init()
40-
{
41-
$this->setInitState(PetitionEnum::DRAFT());
42-
43-
// defines machine's allowed behavior, when no Assertion is given uses DefaultCallbackAssertion
44-
$this
45-
// prevents changing state upon assertion when AlwaysFalseAssertion is given
46-
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::SENT(), new AlwaysFalseAssertion())
47-
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::CANCELED(), new AlwaysFalseAssertion())
48-
// when no Assertion is given uses DefaultCallbackAssertion which calls assertXToY methods
49-
->allowTransition(PetitionEnum::SENT(), PetitionEnum::VOTED())
50-
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::ACCEPTED())
51-
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::REJECTED());
52-
53-
}
54-
5542
/**
56-
* @throws MachineException
5743
* @throws TransitionException
44+
* @throws ConfigurationException
45+
* @throws SchemaException
5846
*/
5947
public function send()
6048
{
@@ -63,7 +51,8 @@ public function send()
6351
}
6452

6553
/**
66-
* @throws MachineException
54+
* @throws ConfigurationException
55+
* @throws SchemaException
6756
* @throws TransitionException
6857
*/
6958
public function cancel()
@@ -104,9 +93,22 @@ public function onTransition(Transition $transition)
10493
}
10594
}
10695

96+
$Schema = new Schema();
97+
98+
$schema
99+
->setInitState(PetitionEnum::DRAFT())
100+
// prevents changing state upon assertion when AlwaysFalseAssertion is given
101+
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::SENT(), new AlwaysFalseAssertion())
102+
->allowTransition(PetitionEnum::DRAFT(), PetitionEnum::CANCELED(), new AlwaysFalseAssertion())
103+
// when no Assertion is given uses DefaultCallbackAssertion which calls assertXToY methods
104+
->allowTransition(PetitionEnum::SENT(), PetitionEnum::VOTED())
105+
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::ACCEPTED())
106+
->allowTransition(PetitionEnum::VOTED(), PetitionEnum::REJECTED());
107107

108108
$p = new Petition();
109-
$p->init();
109+
$p
110+
->setSchema($schema)
111+
->init();
110112

111113
echo 'Running...' . PHP_EOL;
112114
$p->run();

phpunit.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
syntaxCheck="false">
1212
<testsuites>
1313
<testsuite name="Application Test Suite">
14-
<directory>./tests/</directory>
14+
<directory>./tests/Machine</directory>
15+
<directory>./tests/Schema</directory>
16+
<directory>./tests/Assertion</directory>
1517
</testsuite>
1618
</testsuites>
1719
<filter>

src/Assertion/AlwaysFalseAssertion.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
namespace Coff\SMF\Assertion;
55

6+
use Coff\SMF\MachineInterface;
7+
use Coff\SMF\Transition\TransitionInterface;
8+
69
/**
710
* False Assertion for unit testing
811
*/
912
class AlwaysFalseAssertion extends Assertion
1013
{
11-
public function make(): bool
14+
public function make(MachineInterface $machine, TransitionInterface $transition): bool
1215
{
1316
return false;
1417
}

src/Assertion/AlwaysTrueAssertion.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
namespace Coff\SMF\Assertion;
55

66

7+
use Coff\SMF\MachineInterface;
8+
use Coff\SMF\Transition\TransitionInterface;
9+
710
class AlwaysTrueAssertion extends Assertion
811
{
912

10-
public function make(): bool
13+
public function make(MachineInterface $machine, TransitionInterface $transition): bool
1114
{
1215
return true;
1316
}

src/Assertion/AssertionInterface.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
namespace Coff\SMF\Assertion;
55

66

7+
use Coff\SMF\MachineInterface;
8+
use Coff\SMF\Transition\TransitionInterface;
9+
710
interface AssertionInterface
811
{
9-
public function make(): bool;
12+
public function make(MachineInterface $machine, TransitionInterface $transition): bool;
1013
}

src/Assertion/CallbackAssertion.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55

66
use Coff\SMF\Exception\AssertionException;
7+
use Coff\SMF\MachineInterface;
8+
use Coff\SMF\Transition\TransitionInterface;
79

810
class CallbackAssertion extends Assertion
911
{
@@ -12,13 +14,13 @@ class CallbackAssertion extends Assertion
1214
protected $callback;
1315

1416
/** @var array */
15-
protected $params;
17+
protected $extraParams = [];
1618

17-
public function __construct(callable $callback = null, array $params = [])
19+
public function __construct(callable $callback = null, array $extra_params = [])
1820
{
1921
$this->callback = $callback;
2022

21-
$this->params = $params;
23+
$this->extraParams = $extra_params;
2224
}
2325

2426
public function setCallback(callable $callback)
@@ -28,21 +30,23 @@ public function setCallback(callable $callback)
2830
return $this;
2931
}
3032

31-
public function setParams($params = [])
33+
public function setExtraParams($extra_params = [])
3234
{
33-
$this->params = $params;
35+
$this->extraParams = $extra_params;
3436

3537
return $this;
3638
}
3739

3840
/**
41+
* @param MachineInterface $machine
42+
* @param TransitionInterface $transition
3943
* @return bool
4044
* @throws AssertionException
4145
*/
42-
public function make(): bool
46+
public function make(MachineInterface $machine, TransitionInterface $transition): bool
4347
{
4448
if ($this->callback) {
45-
return call_user_func_array($this->callback, $this->params);
49+
return call_user_func_array($this->callback, array_merge([$machine, $transition], $this->extraParams));
4650
} else {
4751
throw new AssertionException('Callback not configured!');
4852
}

src/Assertion/CommonCallbackAssertion.php

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,25 @@
44
namespace Coff\SMF\Assertion;
55

66

7-
use Coff\SMF\Exception\AssertionException;
87
use Coff\SMF\MachineInterface;
98
use Coff\SMF\Transition\TransitionInterface;
109

1110
class CommonCallbackAssertion extends CallbackAssertion
1211
{
13-
/** @var MachineInterface $object */
14-
protected $object;
15-
16-
/** @var TransitionInterface $transition */
17-
protected $transition;
1812

1913
public function __construct()
2014
{
2115
parent::__construct();
2216
}
2317

24-
25-
public function setObject(MachineInterface $object)
26-
{
27-
$this->object = $object;
28-
29-
return $this;
30-
}
31-
32-
public function setTransition(TransitionInterface $transition)
33-
{
34-
$this->transition = $transition;
35-
36-
return $this;
37-
}
38-
3918
/**
19+
* @param MachineInterface $machine
20+
* @param TransitionInterface $transition
4021
* @return bool
41-
* @throws AssertionException
4222
*/
43-
public function make(): bool
23+
public function make(MachineInterface $machine, TransitionInterface $transition): bool
4424
{
45-
if ($this->object instanceof MachineInterface && $this->transition instanceof TransitionInterface) {
46-
return call_user_func_array([$this->object, 'assertTransition'], [$this->transition]);
47-
} else {
48-
throw new AssertionException('Callback not configured!');
49-
}
25+
return call_user_func_array([$machine, 'assertTransition'], [$transition]);
5026
}
5127

5228
}

0 commit comments

Comments
 (0)