Skip to content

Commit 185941e

Browse files
committed
feature #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 #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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy