From 8345a62ce41b5aee5f84a1e62eff3907e4213f8a Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 13 Nov 2023 14:52:26 +0100 Subject: [PATCH] [Workflow] Add `AsMarkingStore` attribute --- .../FrameworkBundle/FrameworkBundle.php | 2 + .../Workflow/Attribute/AsMarkingStore.php | 31 +++++++++ src/Symfony/Component/Workflow/CHANGELOG.md | 5 ++ .../WorkflowMarkingStorePass.php | 49 +++++++++++++ .../MarkingStore/AbstractMarkingStore.php | 22 ++++++ .../MarkingStore/MethodMarkingStore.php | 5 +- .../WorkflowMarkingStorePassTest.php | 69 +++++++++++++++++++ .../Tests/Fixtures/AttributeMarkingStore.php | 26 +++++++ ...ttributeMarkingStoreWithCustomProperty.php | 30 ++++++++ ...ttributeMarkingStoreWithoutConstructor.php | 13 ++++ 10 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Component/Workflow/Attribute/AsMarkingStore.php create mode 100644 src/Symfony/Component/Workflow/DependencyInjection/WorkflowMarkingStorePass.php create mode 100644 src/Symfony/Component/Workflow/MarkingStore/AbstractMarkingStore.php create mode 100644 src/Symfony/Component/Workflow/Tests/DependencyInjection/WorkflowMarkingStorePassTest.php create mode 100644 src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStore.php create mode 100644 src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithCustomProperty.php create mode 100644 src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithoutConstructor.php diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 25f9637867943..edc19e19a9586 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -71,6 +71,7 @@ use Symfony\Component\VarExporter\Internal\Registry; use Symfony\Component\Workflow\DependencyInjection\WorkflowDebugPass; use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass; +use Symfony\Component\Workflow\DependencyInjection\WorkflowMarkingStorePass; // Help opcache.preload discover always-needed symbols class_exists(ApcuAdapter::class); @@ -157,6 +158,7 @@ public function build(ContainerBuilder $container): void $container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING); $this->addCompilerPassIfExists($container, FormPass::class); $this->addCompilerPassIfExists($container, WorkflowGuardListenerPass::class); + $this->addCompilerPassIfExists($container, WorkflowMarkingStorePass::class); $container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32); $container->addCompilerPass(new RegisterLocaleAwareServicesPass()); $container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32); diff --git a/src/Symfony/Component/Workflow/Attribute/AsMarkingStore.php b/src/Symfony/Component/Workflow/Attribute/AsMarkingStore.php new file mode 100644 index 0000000000000..76ab9e3d66f60 --- /dev/null +++ b/src/Symfony/Component/Workflow/Attribute/AsMarkingStore.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +/** + * @author Alexandre Daubois + */ +#[\Attribute(\Attribute::TARGET_CLASS)] +final class AsMarkingStore +{ + public function __construct( + /** + * The name of the property where the marking will be stored in the subject. + */ + public string $markingName, + /** + * The name of the property where the marking name will be stored. + */ + public string $property = 'property', + ) { + } +} diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md index 00840acee36c1..fb5a9b876beec 100644 --- a/src/Symfony/Component/Workflow/CHANGELOG.md +++ b/src/Symfony/Component/Workflow/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add `AsMarkingStore` attribute + 7.0 --- diff --git a/src/Symfony/Component/Workflow/DependencyInjection/WorkflowMarkingStorePass.php b/src/Symfony/Component/Workflow/DependencyInjection/WorkflowMarkingStorePass.php new file mode 100644 index 0000000000000..23eff7b0a6d2e --- /dev/null +++ b/src/Symfony/Component/Workflow/DependencyInjection/WorkflowMarkingStorePass.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\DependencyInjection; + +use Symfony\Component\DependencyInjection\ChildDefinition; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\Workflow\Attribute\AsMarkingStore; + +/** + * @author Alexandre Daubois + */ +class WorkflowMarkingStorePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + // Register marking store + $container->registerAttributeForAutoconfiguration(AsMarkingStore::class, static function (ChildDefinition $definition, AsMarkingStore $attribute, \ReflectionClass $reflector): void { + $tagAttributes = get_object_vars($attribute); + $invalid = true; + + if ($constructor = $reflector->getConstructor()) { + foreach ($constructor->getParameters() as $parameters) { + if ($tagAttributes['property'] === $parameters->getName()) { + $type = $parameters->getType(); + $invalid = !$type instanceof \ReflectionNamedType || 'string' !== $type->getName(); + } + } + } + + if ($invalid) { + throw new LogicException(sprintf('The "%s" class doesn\'t have a constructor with a string type-hinted argument named "%s".', $reflector->getName(), $tagAttributes['property'])); + } + + $definition->replaceArgument('$'.$tagAttributes['property'], $tagAttributes['markingName']); + $definition->addTag('workflow.marking_store', $tagAttributes); + }); + } +} diff --git a/src/Symfony/Component/Workflow/MarkingStore/AbstractMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/AbstractMarkingStore.php new file mode 100644 index 0000000000000..94a6e6df758ba --- /dev/null +++ b/src/Symfony/Component/Workflow/MarkingStore/AbstractMarkingStore.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\MarkingStore; + +/** + * @author Alexandre Daubois + */ +abstract class AbstractMarkingStore implements MarkingStoreInterface +{ + public function __construct(protected string $property) + { + } +} diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php index 773328f150e14..72ca1493923a7 100644 --- a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php +++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php @@ -29,7 +29,7 @@ * * @author Grégoire Pineau */ -final class MethodMarkingStore implements MarkingStoreInterface +final class MethodMarkingStore extends AbstractMarkingStore { /** @var array */ private array $getters = []; @@ -43,8 +43,9 @@ final class MethodMarkingStore implements MarkingStoreInterface */ public function __construct( private bool $singleState = false, - private string $property = 'marking', + string $property = 'marking', ) { + parent::__construct($property); } public function getMarking(object $subject): Marking diff --git a/src/Symfony/Component/Workflow/Tests/DependencyInjection/WorkflowMarkingStorePassTest.php b/src/Symfony/Component/Workflow/Tests/DependencyInjection/WorkflowMarkingStorePassTest.php new file mode 100644 index 0000000000000..7bc5153d8fa8b --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/DependencyInjection/WorkflowMarkingStorePassTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\Workflow\DependencyInjection\WorkflowMarkingStorePass; +use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStore; +use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithCustomProperty; +use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithoutConstructor; + +class WorkflowMarkingStorePassTest extends TestCase +{ + private ContainerBuilder $container; + private WorkflowMarkingStorePass $compilerPass; + + protected function setUp(): void + { + $this->container = new ContainerBuilder(); + $this->compilerPass = new WorkflowMarkingStorePass(); + } + + public function testRegistersMarkingStore() + { + $this->container->register(AttributeMarkingStore::class, AttributeMarkingStore::class)->setPublic(true)->setAutoconfigured(true); + + (new AttributeAutoconfigurationPass())->process($this->container); + $this->compilerPass->process($this->container); + $this->container->compile(); + + $this->assertSame('currentPlace', $this->container->get(AttributeMarkingStore::class)->getProperty()); + $this->assertSame(['currentPlace'], $this->container->getDefinition(AttributeMarkingStore::class)->getArguments()); + } + + public function testRegistersMarkingStoreWithCustomPropertyForMarking() + { + $this->container->register(AttributeMarkingStoreWithCustomProperty::class, AttributeMarkingStoreWithCustomProperty::class)->setPublic(true)->setAutoconfigured(true); + + (new AttributeAutoconfigurationPass())->process($this->container); + $this->compilerPass->process($this->container); + $this->container->compile(); + + $this->assertSame('currentPlace', $this->container->get(AttributeMarkingStoreWithCustomProperty::class)->getAnother()); + $this->assertSame(['currentPlace'], $this->container->getDefinition(AttributeMarkingStoreWithCustomProperty::class)->getArguments()); + } + + public function testRegisterMakingStoreWithoutConstructor() + { + $this->container->register(AttributeMarkingStoreWithoutConstructor::class, AttributeMarkingStoreWithoutConstructor::class)->setPublic(true)->setAutoconfigured(true); + + (new AttributeAutoconfigurationPass())->process($this->container); + $this->compilerPass->process($this->container); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithoutConstructor" class doesn\'t have a constructor with a string type-hinted argument named "another".'); + $this->container->compile(); + } +} diff --git a/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStore.php b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStore.php new file mode 100644 index 0000000000000..1641b89741230 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStore.php @@ -0,0 +1,26 @@ +{$this->property} => 1]); + } + + public function setMarking(object $subject, Marking $marking, array $context = []): void + { + $subject->{$this->property} = key($marking->getPlaces()); + } + + public function getProperty(): string + { + return $this->property; + } +} diff --git a/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithCustomProperty.php b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithCustomProperty.php new file mode 100644 index 0000000000000..cb76a62970862 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithCustomProperty.php @@ -0,0 +1,30 @@ +another; + } +} diff --git a/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithoutConstructor.php b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithoutConstructor.php new file mode 100644 index 0000000000000..829b390774a39 --- /dev/null +++ b/src/Symfony/Component/Workflow/Tests/Fixtures/AttributeMarkingStoreWithoutConstructor.php @@ -0,0 +1,13 @@ +