Skip to content

[Workflow] Add AsMarkingStore attribute #52566

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
31 changes: 31 additions & 0 deletions src/Symfony/Component/Workflow/Attribute/AsMarkingStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <alex.daubois@gmail.com>
*/
#[\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',
) {
}
}
5 changes: 5 additions & 0 deletions src/Symfony/Component/Workflow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

7.1
---

* Add `AsMarkingStore` attribute

7.0
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <alex.daubois@gmail.com>
*/
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);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <alex.daubois@gmail.com>
*/
abstract class AbstractMarkingStore implements MarkingStoreInterface
{
public function __construct(protected string $property)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
final class MethodMarkingStore implements MarkingStoreInterface
final class MethodMarkingStore extends AbstractMarkingStore
{
/** @var array<class-string, MarkingStoreMethod> */
private array $getters = [];
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Symfony\Component\Workflow\Tests\Fixtures;

use Symfony\Component\Workflow\Attribute\AsMarkingStore;
use Symfony\Component\Workflow\Marking;
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;

#[AsMarkingStore('currentPlace')]
class AttributeMarkingStore extends AbstractMarkingStore
{
public function getMarking(object $subject): Marking
{
return new Marking([$subject->{$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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Symfony\Component\Workflow\Tests\Fixtures;

use Symfony\Component\Workflow\Attribute\AsMarkingStore;
use Symfony\Component\Workflow\Marking;
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;

#[AsMarkingStore(markingName: 'currentPlace', property: 'another')]
class AttributeMarkingStoreWithCustomProperty implements MarkingStoreInterface
{
public function __construct(private string $another)
{
}

public function getMarking(object $subject): Marking
{
return new Marking();
}

public function setMarking(object $subject, Marking $marking, array $context = []): void
{
}

public function getAnother(): string
{
return $this->another;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Symfony\Component\Workflow\Tests\Fixtures;

use Symfony\Component\Workflow\Attribute\AsMarkingStore;
use Symfony\Component\Workflow\Marking;
use Symfony\Component\Workflow\MarkingStore\AbstractMarkingStore;
use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface;

#[AsMarkingStore(markingName: 'currentPlace', property: 'another')]
class AttributeMarkingStoreWithoutConstructor
{
}