8000 feature #50974 [Workflow] Add support for storing the marking in a pr… · zim32/symfony@185941e · GitHub
[go: up one dir, main page]

Skip to content

Commit 185941e

Browse files
committed
feature symfony#50974 [Workflow] Add support for storing the marking in a property (lyrixx)
This PR was merged into the 6.4 branch. Discussion ---------- [Workflow] Add support for storing the marking in a property | Q | A | ------------- | --- | Branch? | 6.4 | Bug fix? | no | New feature? | yes | Deprecations? | no | Tickets | fixes symfony#49778 | License | MIT | Doc PR | Commits ------- 5174250 [Workflow] Add support for storing the marking in a property
2 parents d660091 + 5174250 commit 185941e

File tree

5 files changed

+206
-29
lines changed

5 files changed

+206
-29
lines changed

src/Symfony/Component/Workflow/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Add `with-metadata` option to the `workflow:dump` command to include places,
88
transitions and workflow's metadata into dumped graph
9+
* Add support for storing marking in a property
910

1011
6.2
1112
---

src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,43 @@
1515
use Symfony\Component\Workflow\Marking;
1616

1717
/**
18-
* MethodMarkingStore stores the marking with a subject's method.
18+
* MethodMarkingStore stores the marking with a subject's public method
19+
* or public property.
1920
*
20-
* This store deals with a "single state" or "multiple state" Marking.
21+
* This store deals with a "single state" or "multiple state" marking.
2122
*
22-
* "single state" Marking means a subject can be in one and only one state at
23-
* the same time. Use it with state machine.
23+
* "single state" marking means a subject can be in one and only one state at
24+
* the same time. Use it with state machine. It uses a string to store the
25+
* marking.
2426
*
25-
* "multiple state" Marking means a subject can be in many states at the same
26-
* time. Use it with workflow.
27+
* "multiple state" marking means a subject can be in many states at the same
28+
* time. Use it with workflow. It uses an array of strings to store the marking.
2729
*
2830
* @author Grégoire Pineau <lyrixx@lyrixx.info>
2931
*/
3032
final class MethodMarkingStore implements MarkingStoreInterface
3133
{
32-
private bool $singleState;
33-
private string $property;
34+
/** @var array<class-string, MarkingStoreMethod> */
35+
private array $getters = [];
36+
/** @var array<class-string, MarkingStoreMethod> */
37+
private array $setters = [];
3438

3539
/**
36-
* @param string $property Used to determine methods to call
37-
* The `getMarking` method will use `$subject->getProperty()`
38-
* The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = array())`
40+
* @param string $property Used to determine methods or property to call
41+
* The `getMarking` method will use `$subject->getProperty()` or `$subject->property`
42+
* The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = [])` or `$subject->property = string|array $places`
3943
*/
40-
public function __construct(bool $singleState = false, string $property = 'marking')
41-
{
42-
$this->singleState = $singleState;
43-
$this->property = $property;
44+
public function __construct(
45+
private bool $singleState = false,
46+
private string $property = 'marking',
47+
) {
4448
}
4549

4650
public function getMarking(object $subject): Marking
4751
{
48-
$method = 'get'.ucfirst($this->property);
49-
50-
if (!method_exists($subject, $method)) {
51-
throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method));
52-
}
53-
5452
$marking = null;
5553
try {
56-
$marking = $subject->{$method}();
54+
$marking = ($this->getGetter($subject))();
5755
} catch (\Error $e) {
5856
$unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property);
5957
if ($e->getMessage() !== $unInitializedPropertyMessage) {
@@ -68,7 +66,7 @@ public function getMarking(object $subject): Marking
6866
if ($this->singleState) {
6967
$marking = [(string) $marking => 1];
7068
} elseif (!\is_array($marking)) {
71-
throw new LogicException(sprintf('The method "%s::%s()" did not return an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $method));
69+
throw new LogicException(sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property));
7270
}
7371

7472
return new Marking($marking);
@@ -82,12 +80,53 @@ public function setMarking(object $subject, Marking $marking, array $context = [
8280
$marking = key($marking);
8381
}
8482

85-
$method = 'set'.ucfirst($this->property);
83+
($this->getSetter($subject))($marking, $context);
84+
}
85+
86+
private function getGetter(object $subject): callable
87+
{
88+
$property = $this->property;
89+
$method = 'get'.ucfirst($property);
90+
91+
return match ($this->getters[$subject::class] ??= $this->getType($subject, $property, $method)) {
92+
MarkingStoreMethod::METHOD => $subject->{$method}(...),
93+
MarkingStoreMethod::PROPERTY => static fn () => $subject->{$property},
94+
};
95+
}
8696

87-
if (!method_exists($subject, $method)) {
88-
throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method));
97+
private function getSetter(object $subject): callable
98+
{
99+
$property = $this->property;
100+
$method = 'set'.ucfirst($property);
101+
102+
return match ($this->setters[$subject::class] ??= $this->getType($subject, $property, $method)) {
103+
MarkingStoreMethod::METHOD => $subject->{$method}(...),
104+
MarkingStoreMethod::PROPERTY => static fn ($marking) => $subject->{$property} = $marking,
105+
};
106+
}
107+
108+
private static function getType(object $subject, string $property, string $method): MarkingStoreMethod
109+
{
110+
if (method_exists($subject, $method) && (new \ReflectionMethod($subject, $method))->isPublic()) {
111+
return MarkingStoreMethod::METHOD;
112+
}
113+
114+
try {
115+
if ((new \ReflectionProperty($subject, $property))->isPublic()) {
116+
return MarkingStoreMethod::PROPERTY;
117+
}
118+
} catch (\ReflectionException) {
89119
}
90120

91-
$subject->{$method}($marking, $context);
121+
throw new LogicException(sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property));
92122
}
93123
}
124+
125+
/**
126+
* @internal
127+
*/
128+
enum MarkingStoreMethod
129+
{
130+
case METHOD;
131+
case PROPERTY;
132+
}

src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ public function testGetSetMarkingWithMultipleState()
2929

3030
$marking->mark('first_place');
3131

32-
$markingStore->setMarking($subject, $marking);
32+
$markingStore->setMarking($subject, $marking, ['foo' => 'bar']);
3333

3434
$this->assertSame(['first_place' => 1], $subject->getMarking());
35+
$this->assertSame(['foo' => 'bar'], $subject->getContext());
3536

3637
$marking2 = $markingStore->getMarking($subject);
3738

@@ -50,11 +51,12 @@ public function testGetSetMarkingWithSingleState()
5051

5152
$marking->mark('first_place');
5253

53-
$markingStore->setMarking($subject, $marking);
54+
$markingStore->setMarking($subject, $marking, ['foo' => 'bar']);
5455

5556
$this->assertSame('first_place', $subject->getMarking());
5657

5758
$marking2 = $markingStore->getMarking($subject);
59+
$this->assertSame(['foo' => 'bar'], $subject->getContext());
5860

5961
$this->assertEquals($marking, $marking2);
6062
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\Tests\MarkingStore;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore;
16+
17+
class PropertiesMarkingStoreTest extends TestCase
18+
{
19+
public function testGetSetMarkingWithMultipleState()
20+
{
21+
$subject = new SubjectWithProperties();
22+
$markingStore = new MethodMarkingStore(false);
23+
24+
$marking = $markingStore->getMarking($subject);
25+
26+
$this->assertCount(0, $marking->getPlaces());
27+
28+
$marking->mark('first_place');
29+
30+
$markingStore->setMarking($subject, $marking, ['foo' => 'bar']);
31+
32+
$this->assertSame(['first_place' => 1], $subject->marking);
33+
34+
$marking2 = $markingStore->getMarking($subject);
35+
36+
$this->assertEquals($marking, $marking2);
37+
}
38+
39+
public function testGetSetMarkingWithSingleState()
40+
{
41+
$subject = new SubjectWithProperties();
42+
$markingStore = new MethodMarkingStore(true, 'place', 'placeContext');
43+
44+
$marking = $markingStore->getMarking($subject);
45+
46+
$this->assertCount(0, $marking->getPlaces());
47+
48+
$marking->mark('first_place');
49+
50+
$markingStore->setMarking($subject, $marking, ['foo' => 'bar']);
51+
52+
$this->assertSame('first_place', $subject->place);
53+
54+
$marking2 = $markingStore->getMarking($subject);
55+
56+
$this->assertEquals($marking, $marking2);
57+
}
58+
59+
public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName()
60+
{
61+
$subject = new SubjectWithProperties();
62+
$subject->place = 0;
63+
64+
$markingStore = new MethodMarkingStore(true, 'place');
65+
66+
$marking = $markingStore->getMarking($subject);
67+
68+
$this->assertCount(1, $marking->getPlaces());
69+
}
70+
71+
public function testGetMarkingWithValueObject()
72+
{
73+
$subject = new SubjectWithProperties();
74+
$subject->place = $this->createValueObject('first_place');
75+
76+
$markingStore = new MethodMarkingStore(true, 'place');
77+
78+
$marking = $markingStore->getMarking($subject);
79+
80+
$this->assertCount(1, $marking->getPlaces());
81+
$this->assertSame('first_place', (string) $subject->place);
82+
}
83+
84+
public function testGetMarkingWithUninitializedProperty()
85+
{
86+
$subject = new SubjectWithProperties();
87+
88+
$markingStore = new MethodMarkingStore(true, 'place');
89+
90+
$marking = $markingStore->getMarking($subject);
91+
92+
$this->assertCount(0, $marking->getPlaces());
93+
}
94+
95+
private function createValueObject(string $markingValue): object
96+
{
97+
return new class($markingValue) {
98+
public function __construct(
99+
private string $markingValue,
100+
) {
101+
}
102+
103+
public function __toString(): string
104+
{
105+
return $this->markingValue;
106+
}
107+
};
108+
}
109+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Workflow\Tests\MarkingStore;
13+
14+
final class SubjectWithProperties
15+
{
16+
// for type=workflow
17+
public array $marking;
18+
19+
// for type=state_machine
20+
public string $place;
21+
22+
private function getMarking(): array
23+
{
24+
return $this->marking;
25+
}
26+
}

0 commit comments

Comments
 (0)
0