Skip to content

Commit 5b4e9a9

Browse files
committed
[Workflow] Add support for workflows that need to store many tokens in the marking
1 parent 20d5453 commit 5b4e9a9

File tree

6 files changed

+207
-22
lines changed

6 files changed

+207
-22
lines changed

UPGRADE-7.1.md

+1
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ Workflow
4646
--------
4747

4848
* Add method `getEnabledTransition()` to `WorkflowInterface`
49+
* Add `$nbToken` argument to `Marking::mark()` and `Marking::unmark()`

src/Symfony/Component/Workflow/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add method `getEnabledTransition()` to `WorkflowInterface`
88
* Automatically register places from transitions
9+
* Add support for workflows that need to store many tokens in the marking
910

1011
7.0
1112
---

src/Symfony/Component/Workflow/Marking.php

+43-6
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,60 @@ class Marking
2222
private ?array $context = null;
2323

2424
/**
25-
* @param int[] $representation Keys are the place name and values should be 1
25+
* @param int[] $representation Keys are the place name and values should be superior or equals to 1
2626
*/
2727
public function __construct(array $representation = [])
2828
{
2929
foreach ($representation as $place => $nbToken) {
30-
$this->mark($place);
30+
$this->mark($place, $nbToken);
3131
}
3232
}
3333

34-
public function mark(string $place): void
34+
/**
35+
* @param int $nbToken
36+
*
37+
* @psalm-param int<1, max> $nbToken
38+
*/
39+
public function mark(string $place /* , int $nbToken = 1 */): void
3540
{
36-
$this->places[$place] = 1;
41+
$nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1;
42+
43+
if ($nbToken < 1) {
44+
throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken));
45+
}
46+
47+
$this->places[$place] ??= 0;
48+
$this->places[$place] += $nbToken;
3749
}
3850

39-
public function unmark(string $place): void
51+
/**
52+
* @param int $nbToken
53+
*
54+
* @psalm-param int<1, max> $nbToken
55+
*/
56+
public function unmark(string $place /* , int $nbToken = 1 */): void
4057
{
41-
unset($this->places[$place]);
58+
$nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1;
59+
60+
if ($nbToken < 1) {
61+
throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken));
62+
}
63+
64+
if (!$this->has($place)) {
65+
throw new \InvalidArgumentException(sprintf('The place "%s" is not marked.', $place));
66+
}
67+
68+
$tokenCount = $this->places[$place] - $nbToken;
69+
70+
if (0 > $tokenCount) {
71+
throw new \InvalidArgumentException(sprintf('The place "%s" could not contain a negative token number: "%s" (initial) - "%s" (nbToken) = "%s".', $place, $this->places[$place], $nbToken, $tokenCount));
72+
}
73+
74+
if (0 === $tokenCount) {
75+
unset($this->places[$place]);
76+
} else {
77+
$this->places[$place] = $tokenCount;
78+
}
4279
}
4380

4481
public function has(string $place): bool

src/Symfony/Component/Workflow/Tests/MarkingTest.php

+50-4
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,70 @@ public function testMarking()
2222

2323
$this->assertTrue($marking->has('a'));
2424
$this->assertFalse($marking->has('b'));
25-
$this->assertSame(['a' => 1], $marking->getPlaces());
25+
$this->assertPlaces(['a' => 1], $marking);
2626

2727
$marking->mark('b');
2828

2929
$this->assertTrue($marking->has('a'));
3030
$this->assertTrue($marking->has('b'));
31-
$this->assertSame(['a' => 1, 'b' => 1], $marking->getPlaces());
31+
$this->assertPlaces(['a' => 1, 'b' => 1], $marking);
3232

3333
$marking->unmark('a');
3434

3535
$this->assertFalse($marking->has('a'));
3636
$this->assertTrue($marking->has('b'));
37-
$this->assertSame(['b' => 1], $marking->getPlaces());
37+
$this->assertPlaces(['b' => 1], $marking);
3838

3939
$marking->unmark('b');
4040

4141
$this->assertFalse($marking->has('a'));
4242
$this->assertFalse($marking->has('b'));
43-
$this->assertSame([], $marking->getPlaces());
43+
$this->assertPlaces([], $marking);
44+
45+
$marking->mark('a');
46+
$this->assertPlaces(['a' => 1], $marking);
47+
48+
$marking->mark('a');
49+
$this->assertPlaces(['a' => 2], $marking);
50+
51+
$marking->unmark('a');
52+
$this->assertPlaces(['a' => 1], $marking);
53+
54+
$marking->unmark('a');
55+
$this->assertPlaces([], $marking);
56+
}
57+
58+
public function testGuardNotMarked()
59+
{
60+
$marking = new Marking([]);
61+
62+
$this->expectException(\LogicException::class);
63+
$this->expectExceptionMessage('The place "a" is not marked.');
64+
$marking->unmark('a');
65+
}
66+
67+
public function testUnmarkGuardResultTokenCountIsNotNegative()
68+
{
69+
$marking = new Marking(['a' => 1]);
70+
71+
$this->expectException(\LogicException::class);
72+
$this->expectExceptionMessage('The place "a" could not contain a negative token number: "1" (initial) - "2" (nbToken) = "-1".');
73+
$marking->unmark('a', 2);
74+
}
75+
76+
public function testUnmarkGuardNbTokenIsGreaterThanZero()
77+
{
78+
$marking = new Marking(['a' => 1]);
79+
80+
$this->expectException(\LogicException::class);
81+
$this->expectExceptionMessage('The number of tokens must be greater than 0, "0" given.');
82+
$marking->unmark('a', 0);
83+
}
84+
85+
private function assertPlaces(array $expected, Marking $marking)
86+
{
87+
$places = $marking->getPlaces();
88+
ksort($places);
89+
$this->assertSame($expected, $places);
4490
}
4591
}

src/Symfony/Component/Workflow/Tests/WorkflowBuilderTrait.php

+39
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,43 @@ private static function createComplexStateMachineDefinition(): Definition
158158
// | d | -------------+
159159
// +-----+
160160
}
161+
162+
private static function createWorkflowWithSameNameBackTransition(): Definition
163+
{
164+
$places = range('a', 'c');
165+
166+
$transitions = [];
167+
$transitions[] = new Transition('a_to_bc', 'a', ['b', 'c']);
168+
$transitions[] = new Transition('back1', 'b', 'a');
169+
$transitions[] = new Transition('back1', 'c', 'b');
170+
$transitions[] = new Transition('back2', 'c', 'b');
171+
$transitions[] = new Transition('back2', 'b', 'a');
172+
$transitions[] = new Transition('c_to_cb', 'c', ['b', 'c']);
173+
174+
return new Definition($places, $transitions);
175+
176+
// The graph looks like:
177+
// +-----------------------------------------------------------------+
178+
// | |
179+
// | |
180+
// | +---------------------------------------------+ |
181+
// v | v |
182+
// +---+ +---------+ +-------+ +---------+ +---+ +-------+
183+
// | a | --> | a_to_bc | --> | | --> | back2 | --> | | --> | back2 |
184+
// +---+ +---------+ | | +---------+ | | +-------+
185+
// ^ | | | |
186+
// | | c | <-----+ | b |
187+
// | | | | | |
188+
// | | | +---------+ | | +-------+
189+
// | | | --> | c_to_cb | --> | | --> | back1 |
190+
// | +-------+ +---------+ +---+ +-------+
191+
// | | ^ |
192+
// | | | |
193+
// | v | |
194+
// | +-------+ | |
195+
// | | back1 | ----------------------+ |
196+
// | +-------+ |
197+
// | |
198+
// +-----------------------------------------------------------------+
199+
}
161200
}

src/Symfony/Component/Workflow/Tests/WorkflowTest.php

+73-12
Original file line numberDiff line numberDiff line change
@@ -319,28 +319,32 @@ public function testApplyWithSameNameTransition()
319319

320320
$marking = $workflow->apply($subject, 'a_to_bc');
321321

322-
$this->assertFalse($marking->has('a'));
323-
$this->assertTrue($marking->has('b'));
324-
$this->assertTrue($marking->has('c'));
322+
$this->assertPlaces([
323+
'b' => 1,
324+
'c' => 1,
325+
], $marking);
325326

326327
$marking = $workflow->apply($subject, 'to_a');
327328

328-
$this->assertTrue($marking->has('a'));
329-
$this->assertFalse($marking->has('b'));
330-
$this->assertFalse($marking->has('c'));
329+
// Two tokens in "a"
330+
$this->assertPlaces([
331+
'a' => 2,
332+
], $marking);
331333

332334
$workflow->apply($subject, 'a_to_bc');
333335
$marking = $workflow->apply($subject, 'b_to_c');
334336

335-
$this->assertFalse($marking->has('a'));
336-
$this->assertFalse($marking->has('b'));
337-
$this->assertTrue($marking->has('c'));
337+
$this->assertPlaces([
338+
'a' => 1,
339+
'c' => 2,
340+
], $marking);
338341

339342
$marking = $workflow->apply($subject, 'to_a');
340343

341-
$this->assertTrue($marking->has('a'));
342-
$this->assertFalse($marking->has('b'));
343-
$this->assertFalse($marking->has('c'));
344+
$this->assertPlaces([
345+
'a' => 2,
346+
'c' => 1,
347+
], $marking);
344348
}
345349

346350
public function testApplyWithSameNameTransition2()
@@ -776,6 +780,63 @@ public function testGetEnabledTransitionsWithSameNameTransition()
776780
$this->assertSame('to_a', $transitions[1]->getName());
777781
$this->assertSame('to_a', $transitions[2]->getName());
778782
}
783+
784+
/**
785+
* @@testWith ["back1"]
786+
* ["back2"]
787+
*/
788+
public function testApplyWithSameNameBackTransition(string $transition)
789+
{
790+
$definition = $this->createWorkflowWithSameNameBackTransition();
791+
$workflow = new Workflow($definition, new MethodMarkingStore());
792+
793+
$subject = new Subject();
794+
795+
$marking = $workflow->apply($subject, 'a_to_bc');
796+
$this->assertPlaces([
797+
'b' => 1,
798+
'c' => 1,
799+
], $marking);
800+
801+
$marking = $workflow->apply($subject, $transition);
802+
$this->assertPlaces([
803+
'a' => 1,
804+
'b' => 1,
805+
], $marking);
806+
807+
$marking = $workflow->apply($subject, $transition);
808+
$this->assertPlaces([
809+
'a' => 2,
810+
], $marking);
811+
812+
$marking = $workflow->apply($subject, 'a_to_bc');
813+
$this->assertPlaces([
814+
'a' => 1,
815+
'b' => 1,
816+
'c' => 1,
817+
], $marking);
818+
819+
$marking = $workflow->apply($subject, 'c_to_cb');
820+
$this->assertPlaces([
821+
'a' => 1,
822+
'b' => 2,
823+
'c' => 1,
824+
], $marking);
825+
826+
$marking = $workflow->apply($subject, 'c_to_cb');
827+
$this->assertPlaces([
828+
'a' => 1,
829+
'b' => 3,
830+
'c' => 1,
831+
], $marking);
832+
}
833+
834+
private function assertPlaces(array $expected, Marking $marking)
835+
{
836+
$places = $marking->getPlaces();
837+
ksort($places);
838+
$this->assertSame($expected, $places);
839+
}
779840
}
780841

781842
class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface

0 commit comments

Comments
 (0)