Skip to content

Commit cde5e56

Browse files
[Workflow] Add AsMarkingStore attribute
1 parent 21673dc commit cde5e56

File tree

11 files changed

+277
-2
lines changed

11 files changed

+277
-2
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,27 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
10651065
$container->setDefinition(sprintf('.%s.listener.audit_trail', $workflowId), $listener);
10661066
}
10671067

1068+
// Register marking store
1069+
$container->registerAttributeForAutoconfiguration(Workflow\Attribute\AsMarkingStore::class, static function (ChildDefinition $definition, Workflow\Attribute\AsMarkingStore $attribute, \ReflectionClass $reflector): void {
1070+
$tagAttributes = get_object_vars($attribute);
1071+
1072+
$invalid = true;
1073+
foreach ($reflector->getConstructor()->getParameters() as $parameters) {
1074+
if ($tagAttributes['property'] === $parameters->getName()) {
1075+
$type = $parameters->getType();
1076+
$invalid = !$type instanceof \ReflectionNamedType || 'string' !== $type->getName();
1077+
}
1078+
}
1079+
1080+
if ($invalid) {
1081+
throw new LogicException('The "%s" class doesn\'t have a constructor with a string type-hinted argument named "%s".', $reflector->getName(), $tagAttributes['property']);
1082+
}
1083+
1084+
$definition->replaceArgument('$'.$tagAttributes['property'], $tagAttributes['markingName']);
1085+
1086+
$definition->addTag('workflow.marking_store', $tagAttributes);
1087+
});
1088+
10681089
// Add Guard Listener
10691090
if ($guardsConfiguration) {
10701091
if (!class_exists(ExpressionLanguage::class)) {

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
use Symfony\Component\VarExporter\Internal\Registry;
7272
use Symfony\Component\Workflow\DependencyInjection\WorkflowDebugPass;
7373
use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass;
74+
use Symfony\Component\Workflow\DependencyInjection\WorkflowMarkingStorePass;
7475

7576
// Help opcache.preload discover always-needed symbols
7677
class_exists(ApcuAdapter::class);
@@ -157,6 +158,7 @@ public function build(ContainerBuilder $container): void
157158
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
158159
$this->addCompilerPassIfExists($container, FormPass::class);
159160
$this->addCompilerPassIfExists($container, WorkflowGuardListenerPass::class);
161+
$this->addCompilerPassIfExists($container, WorkflowMarkingStorePass::class);
160162
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
161163
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
162164
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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\Attribute;
13+
14+
/**
15+
* @author Alexandre Daubois <alex.daubois@gmail.com>
16+
*/
17+
#[\Attribute(\Attribute::TARGET_CLASS)]
18+
final class AsMarkingStore
19+
{
20+
public function __construct(
21+
/**
22+
* The name of the property where the marking will be stored in the subject.
23+
*/
24+
public string $markingName,
25+
/**
26+
* The name of the property where the marking name will be stored.
27+
*/
28+
public string $property = 'property',
29+
) {
30+
}
31+
}

src/Symfony/Component/Workflow/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.1
5+
---
6+
7+
* Add `AsMarkingStore` attribute
8+
49
7.0
510
---
611

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\ChildDefinition;
15+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
18+
use Symfony\Component\Workflow\Attribute\AsMarkingStore;
19+
20+
/**
21+
* @author Alexandre Daubois <alex.daubois@gmail.com>
22+
*/
23+
class WorkflowMarkingStorePass implements CompilerPassInterface
24+
{
25+
public function process(ContainerBuilder $container): void
26+
{
27+
// Register marking store
28+
$container->registerAttributeForAutoconfiguration(AsMarkingStore::class, static function (ChildDefinition $definition, AsMarkingStore $attribute, \ReflectionClass $reflector): void {
29+
$tagAttributes = get_object_vars($attribute);
30+
$invalid = true;
31+
32+
if ($constructor = $reflector->getConstructor()) {
33+
foreach ($constructor->getParameters() as $parameters) {
34+
if ($tagAttributes['property'] === $parameters->getName()) {
35+
$type = $parameters->getType();
36+
$invalid = !$type instanceof \ReflectionNamedType || 'string' !== $type->getName();
37+
}
38+
}
39+
}
40+
41+
if ($invalid) {
42+
throw new LogicException(sprintf('The "%s" class doesn\'t have a constructor with a string type-hinted argument named "%s".', $reflector->getName(), $tagAttributes['property']));
43+
}
44+
45+
$definition->replaceArgument('$'.$tagAttributes['property'], $tagAttributes['markingName']);
46+
$definition->addTag('workflow.marking_store', $tagAttributes);
47+
});
48+
}
49+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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\MarkingStore;
13+
14+
/**
15+
* @author Alexandre Daubois <alex.daubois@gmail.com>
16+
*/
17+
abstract class AbstractMarkingStore implements MarkingStoreInterface
18+
{
19+
public function __construct(protected string $property)
20+
{
21+
}
22+
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
*
3030
* @author Grégoire Pineau <lyrixx@lyrixx.info>
3131
*/
32-
final class MethodMarkingStore implements MarkingStoreInterface
32+
final class MethodMarkingStore extends AbstractMarkingStore
3333
{
3434
/** @var array<class-string, MarkingStoreMethod> */
3535
private array $getters = [];
@@ -43,8 +43,9 @@ final class MethodMarkingStore implements MarkingStoreInterface
4343
*/
4444
public function __construct(
4545
private bool $singleState = false,
46-
private string $property = 'marking',
46+
string $property = 'marking',
4747
) {
48+
parent::__construct($property);
4849
}
4950

5051
public function getMarking(object $subject): Marking
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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\DependencyInjection;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\Compiler\AttributeAutoconfigurationPass;
16+
use Symfony\Component\DependencyInjection\ContainerBuilder;
17+
use Symfony\Component\DependencyInjection\Exception\LogicException;
18+
use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface;
19+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
20+
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
21+
use Symfony\Component\Security\Core\Role\RoleHierarchy;
22+
use Symfony\Component\Validator\Validator\ValidatorInterface;
23+
use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass;
24+
use Symfony\Component\Workflow\DependencyInjection\WorkflowMarkingStorePass;
25+
use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStore;
26+
use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithCustomProperty;
27+
use Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithoutConstructor;
28+
29+
class WorkflowMarkingStorePassTest extends TestCase
30+
{
31+
private ContainerBuilder $container;
32+
private WorkflowMarkingStorePass $compilerPass;
33+
34+
protected function setUp(): void
35+
{
36+
$this->container = new ContainerBuilder();
37+
$this->compilerPass = new WorkflowMarkingStorePass();
38+
}
39+
40+
public function testRegistersMarkingStore()
41+
{
42+
$this->container->register(AttributeMarkingStore::class, AttributeMarkingStore::class)->setPublic(true)->setAutoconfigured(true);
43+
44+
(new AttributeAutoconfigurationPass())->process($this->container);
45+
$this->compilerPass->process($this->container);
46+
$this->container->compile();
47+
48+
$this->assertSame('currentPlace', $this->container->get(AttributeMarkingStore::class)->getProperty());
49+
$this->assertSame(['currentPlace'], $this->container->getDefinition(AttributeMarkingStore::class)->getArguments());
50+
}
51+
52+
public function testRegistersMarkingStoreWithCustomPropertyForMarking()
53+
{
54+
$this->container->register(AttributeMarkingStoreWithCustomProperty::class, AttributeMarkingStoreWithCustomProperty::class)->setPublic(true)->setAutoconfigured(true);
55+
56+
(new AttributeAutoconfigurationPass())->process($this->container);
57+
$this->compilerPass->process($this->container);
58+
$this->container->compile();
59+
60+
$this->assertSame('currentPlace', $this->container->get(AttributeMarkingStoreWithCustomProperty::class)->getAnother());
61+
$this->assertSame(['currentPlace'], $this->container->getDefinition(AttributeMarkingStoreWithCustomProperty::class)->getArguments());
62+
}
63+
64+
public function testRegisterMakingStoreWithoutConstructor()
65+
{
66+
$this->container->register(AttributeMarkingStoreWithoutConstructor::class, AttributeMarkingStoreWithoutConstructor::class)->setPublic(true)->setAutoconfigured(true);
67+
68+
(new AttributeAutoconfigurationPass())->process($this->container);
69+
$this->compilerPass->process($this->container);
70+
71+
$this->expectException(LogicException::class);
72+
$this->expectExceptionMessage('The "Symfony\Component\Workflow\Tests\Fixtures\AttributeMarkingStoreWithoutConstructor" class doesn\'t have a constructor with a string type-hinted argument named "another".');
73+
$this->container->compile();
74+
}
75+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\Component\Workflow\Tests\Fixtures;
4+
5+
use Symfony\Component\Workflow\Attribute\AsMarkingStore;
6+
use Symfony\Component\Workflow\Marking;
7+
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;
8+
9+
#[AsMarkingStore('currentPlace')]
10+
class AttributeMarkingStore extends AbstractMarkingStore
11+
{
12+
public function getMarking(object $subject): Marking
13+
{
14+
return new Marking([$subject->{$this->property} => 1]);
15+
}
16+
17+
public function setMarking(object $subject, Marking $marking, array $context = []): void
18+
{
19+
$subject->{$this->property} = key($marking->getPlaces());
20+
}
21+
22+
public function getProperty(): string
23+
{
24+
return $this->property;
25+
}
26+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Symfony\Component\Workflow\Tests\Fixtures;
4+
5+
use Symfony\Component\Workflow\Attribute\AsMarkingStore;
6+
use Symfony\Component\Workflow\Marking;
7+
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;
8+
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
9+
10+
#[AsMarkingStore(markingName: 'currentPlace', property: 'another')]
11+
class AttributeMarkingStoreWithCustomProperty implements MarkingStoreInterface
12+
{
13+
public function __construct(private string $another)
14+
{
15+
}
16+
17+
public function getMarking(object $subject): Marking
18+
{
19+
return new Marking();
20+
}
21+
22+
public function setMarking(object $subject, Marking $marking, array $context = []): void
23+
{
24+
}
25+
26+
public function getAnother(): string
27+
{
28+
return $this->another;
29+
}
30+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace Symfony\Component\Workflow\Tests\Fixtures;
4+
5+
use Symfony\Component\Workflow\Attribute\AsMarkingStore;
6+
use Symfony\Component\Workflow\Marking;
7+
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;
8+
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;
9+
10+
#[AsMarkingStore(markingName: 'currentPlace', property: 'another')]
11+
class AttributeMarkingStoreWithoutConstructor
12+
{
13+
}

0 commit comments

Comments
 (0)