From 51742507d603993bf15348a53791826e86e436fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 25 Jul 2023 14:54:15 +0200 Subject: [PATCH] [Workflow] Add support for storing the marking in a property --- src/Symfony/Component/Workflow/CHANGELOG.md | 1 + .../MarkingStore/MethodMarkingStore.php | 93 ++++++++++----- .../MarkingStore/MethodMarkingStoreTest.php | 6 +- .../PropertiesMarkingStoreTest.php | 109 ++++++++++++++++++ .../MarkingStore/SubjectWithProperties.php | 26 +++++ 5 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 src/Symfony/Component/Workflow/Tests/MarkingStore/PropertiesMarkingStoreTest.php create mode 100644 src/Symfony/Component/Workflow/Tests/MarkingStore/SubjectWithProperties.php diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index ff7162853d0fa..1fc1373198bbd 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `with-metadata` option to the `workflow:dump` command to include places, transitions and workflow's metadata into dumped graph + * Add support for storing marking in a property 6.2 --- diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php index 78d3307e6ac6c..773328f150e14 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php @@ -15,45 +15,43 @@ use Symfony\Component\Workflow\Marking; /** - * MethodMarkingStore stores the marking with a subject's method. + * MethodMarkingStore stores the marking with a subject's public method + * or public property. * - * This store deals with a "single state" or "multiple state" Marking. + * This store deals with a "single state" or "multiple state" marking. * - * "single state" Marking means a subject can be in one and only one state at - * the same time. Use it with state machine. + * "single state" marking means a subject can be in one and only one state at + * the same time. Use it with state machine. It uses a string to store the + * marking. * - * "multiple state" Marking means a subject can be in many states at the same - * time. Use it with workflow. + * "multiple state" marking means a subject can be in many states at the same + * time. Use it with workflow. It uses an array of strings to store the marking. * * @author Grégoire Pineau */ final class MethodMarkingStore implements MarkingStoreInterface { - private bool $singleState; - private string $property; + /** @var array */ + private array $getters = []; + /** @var array */ + private array $setters = []; /** - * @param string $property Used to determine methods to call - * The `getMarking` method will use `$subject->getProperty()` - * The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = array())` + * @param string $property Used to determine methods or property to call + * The `getMarking` method will use `$subject->getProperty()` or `$subject->property` + * The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = [])` or `$subject->property = string|array $places` */ - public function __construct(bool $singleState = false, string $property = 'marking') - { - $this->singleState = $singleState; - $this->property = $property; + public function __construct( + private bool $singleState = false, + private string $property = 'marking', + ) { } public function getMarking(object $subject): Marking { - $method = 'get'.ucfirst($this->property); - - if (!method_exists($subject, $method)) { - throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method)); - } - $marking = null; try { - $marking = $subject->{$method}(); + $marking = ($this->getGetter($subject))(); } catch (\Error $e) { $unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); if ($e->getMessage() !== $unInitializedPropertyMessage) { @@ -68,7 +66,7 @@ public function getMarking(object $subject): Marking if ($this->singleState) { $marking = [(string) $marking => 1]; } elseif (!\is_array($marking)) { - 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)); + 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)); } return new Marking($marking); @@ -82,12 +80,53 @@ public function setMarking(object $subject, Marking $marking, array $context = [ $marking = key($marking); } - $method = 'set'.ucfirst($this->property); + ($this->getSetter($subject))($marking, $context); + } + + private function getGetter(object $subject): callable + { + $property = $this->property; + $method = 'get'.ucfirst($property); + + return match ($this->getters[$subject::class] ??= $this->getType($subject, $property, $method)) { + MarkingStoreMethod::METHOD => $subject->{$method}(...), + MarkingStoreMethod::PROPERTY => static fn () => $subject->{$property}, + }; + } - if (!method_exists($subject, $method)) { - throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method)); + private function getSetter(object $subject): callable + { + $property = $this->property; + $method = 'set'.ucfirst($property); + + return match ($this->setters[$subject::class] ??= $this->getType($subject, $property, $method)) { + MarkingStoreMethod::METHOD => $subject->{$method}(...), + MarkingStoreMethod::PROPERTY => static fn ($marking) => $subject->{$property} = $marking, + }; + } + + private static function getType(object $subject, string $property, string $method): MarkingStoreMethod + { + if (method_exists($subject, $method) && (new \ReflectionMethod($subject, $method))->isPublic()) { + return MarkingStoreMethod::METHOD; + } + + try { + if ((new \ReflectionProperty($subject, $property))->isPublic()) { + return MarkingStoreMethod::PROPERTY; + } + } catch (\ReflectionException) { } - $subject->{$method}($marking, $context); + 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)); } } + +/** + * @internal + */ +enum MarkingStoreMethod +{ + case METHOD; + case PROPERTY; +} diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php index 1efe40667bc7d..af0be682329be 100644 --- a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -29,9 +29,10 @@ public function testGetSetMarkingWithMultipleState() $marking->mark('first_place'); - $markingStore->setMarking($subject, $marking); + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); $this->assertSame(['first_place' => 1], $subject->getMarking()); + $this->assertSame(['foo' => 'bar'], $subject->getContext()); $marking2 = $markingStore->getMarking($subject); @@ -50,11 +51,12 @@ public function testGetSetMarkingWithSingleState() $marking->mark('first_place'); - $markingStore->setMarking($subject, $marking); + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); $this->assertSame('first_place', $subject->getMarking()); $marking2 = $markingStore->getMarking($subject); + $this->assertSame(['foo' => 'bar'], $subject->getContext()); $this->assertEquals($marking, $marking2); } diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/PropertiesMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/PropertiesMarkingStoreTest.php new file mode 100644 index 0000000000000..10548e5c5cf49 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/PropertiesMarkingStoreTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; + +class PropertiesMarkingStoreTest extends TestCase +{ + public function testGetSetMarkingWithMultipleState() + { + $subject = new SubjectWithProperties(); + $markingStore = new MethodMarkingStore(false); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + + $marking->mark('first_place'); + + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); + + $this->assertSame(['first_place' => 1], $subject->marking); + + $marking2 = $markingStore->getMarking($subject); + + $this->assertEquals($marking, $marking2); + } + + public function testGetSetMarkingWithSingleState() + { + $subject = new SubjectWithProperties(); + $markingStore = new MethodMarkingStore(true, 'place', 'placeContext'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + + $marking->mark('first_place'); + + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); + + $this->assertSame('first_place', $subject->place); + + $marking2 = $markingStore->getMarking($subject); + + $this->assertEquals($marking, $marking2); + } + + public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() + { + $subject = new SubjectWithProperties(); + $subject->place = 0; + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(1, $marking->getPlaces()); + } + + public function testGetMarkingWithValueObject() + { + $subject = new SubjectWithProperties(); + $subject->place = $this->createValueObject('first_place'); + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(1, $marking->getPlaces()); + $this->assertSame('first_place', (string) $subject->place); + } + + public function testGetMarkingWithUninitializedProperty() + { + $subject = new SubjectWithProperties(); + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + } + + private function createValueObject(string $markingValue): object + { + return new class($markingValue) { + public function __construct( + private string $markingValue, + ) { + } + + public function __toString(): string + { + return $this->markingValue; + } + }; + } +} diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/SubjectWithProperties.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/SubjectWithProperties.php new file mode 100644 index 0000000000000..7759448d72f7f --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/SubjectWithProperties.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +final class SubjectWithProperties +{ + // for type=workflow + public array $marking; + + // for type=state_machine + public string $place; + + private function getMarking(): array + { + return $this->marking; + } +} 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