diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 421f669c952b0..2788cf019f5be 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -268,7 +268,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
->fixXmlConfig('argument')
->children()
->enumNode('type')
- ->values(['multiple_state', 'single_state'])
+ ->values(['multiple_state', 'single_state', 'method'])
->end()
->arrayNode('arguments')
->beforeNormalization()
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 874aeaa9773b9..a38518b2e9229 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -614,7 +614,8 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
$definitionDefinition->addTag('workflow.definition', [
'name' => $name,
'type' => $type,
- 'marking_store' => isset($workflow['marking_store']['type']) ? $workflow['marking_store']['type'] : null,
+ 'marking_store' => $workflow['marking_store']['type'] ?? null,
+ 'single_state' => 'method' === ($workflow['marking_store']['type'] ?? null) && ($workflow['marking_store']['arguments'][0] ?? false),
]);
// Create MarkingStore
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
index 5f349ea6a0a3c..0bba153b10317 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
@@ -22,6 +22,7 @@
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index 586a3811e2277..b38439349ad3b 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -215,7 +215,7 @@ public function testWorkflows()
$workflowDefinition->getArgument(0),
'Places are passed to the workflow definition'
);
- $this->assertSame(['workflow.definition' => [['name' => 'article', 'type' => 'workflow', 'marking_store' => 'multiple_state']]], $workflowDefinition->getTags());
+ $this->assertSame(['workflow.definition' => [['name' => 'article', 'type' => 'workflow', 'marking_store' => 'multiple_state', 'single_state' => false]]], $workflowDefinition->getTags());
$this->assertCount(4, $workflowDefinition->getArgument(1));
$this->assertSame('draft', $workflowDefinition->getArgument(2));
@@ -237,7 +237,7 @@ public function testWorkflows()
$stateMachineDefinition->getArgument(0),
'Places are passed to the state machine definition'
);
- $this->assertSame(['workflow.definition' => [['name' => 'pull_request', 'type' => 'state_machine', 'marking_store' => 'single_state']]], $stateMachineDefinition->getTags());
+ $this->assertSame(['workflow.definition' => [['name' => 'pull_request', 'type' => 'state_machine', 'marking_store' => 'single_state', 'single_state' => false]]], $stateMachineDefinition->getTags());
$this->assertCount(9, $stateMachineDefinition->getArgument(1));
$this->assertSame('start', $stateMachineDefinition->getArgument(2));
diff --git a/src/Symfony/Component/Workflow/CHANGELOG.md b/src/Symfony/Component/Workflow/CHANGELOG.md
index 4668f2f68af17..7e54a59a61e8e 100644
--- a/src/Symfony/Component/Workflow/CHANGELOG.md
+++ b/src/Symfony/Component/Workflow/CHANGELOG.md
@@ -5,6 +5,7 @@ CHANGELOG
-----
* Trigger `entered` event for subject entering in the Workflow for the first time
+ * Added a context to `Workflow::apply()`. The `MethodMarkingStore` could be used to leverage this feature.
4.1.0
-----
diff --git a/src/Symfony/Component/Workflow/DependencyInjection/ValidateWorkflowsPass.php b/src/Symfony/Component/Workflow/DependencyInjection/ValidateWorkflowsPass.php
index 175a1f5b96d71..9294a1a7fb426 100644
--- a/src/Symfony/Component/Workflow/DependencyInjection/ValidateWorkflowsPass.php
+++ b/src/Symfony/Component/Workflow/DependencyInjection/ValidateWorkflowsPass.php
@@ -59,6 +59,10 @@ private function createValidator($tag)
return new WorkflowValidator(true);
}
- return new WorkflowValidator();
+ if ('multiple_state' === $tag['marking_store']) {
+ return new WorkflowValidator(false);
+ }
+
+ return new WorkflowValidator($tag['single_state'] ?? false);
}
}
diff --git a/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php b/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php
index 76df9cc0d2160..991a2fc0bdad3 100644
--- a/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php
+++ b/src/Symfony/Component/Workflow/MarkingStore/MarkingStoreInterface.php
@@ -36,8 +36,7 @@ public function getMarking($subject);
/**
* Sets a Marking to a subject.
*
- * @param object $subject A subject
- * @param Marking $marking A marking
+ * @param object $subject A subject
*/
- public function setMarking($subject, Marking $marking);
+ public function setMarking($subject, Marking $marking, array $context = []);
}
diff --git a/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php
new file mode 100644
index 0000000000000..e2e61e7278d85
--- /dev/null
+++ b/src/Symfony/Component/Workflow/MarkingStore/MethodMarkingStore.php
@@ -0,0 +1,83 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Workflow\MarkingStore;
+
+use Symfony\Component\Workflow\Exception\LogicException;
+use Symfony\Component\Workflow\Marking;
+
+/**
+ * MethodMarkingStore stores the marking with a subject's method.
+ *
+ * This store deals with a "single state" or "multiple state" Marking.
+ *
+ * @author Grégoire Pineau
+ */
+class MethodMarkingStore implements MarkingStoreInterface
+{
+ private $singleState;
+ private $property;
+
+ /**
+ * @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())`
+ */
+ public function __construct(bool $singleState = false, string $property = 'marking')
+ {
+ $this->singleState = $singleState;
+ $this->property = $property;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMarking($subject)
+ {
+ $method = 'get'.ucfirst($this->property);
+
+ if (!method_exists($subject, $method)) {
+ throw new LogicException(sprintf('The method "%s::%s()" does not exists.', \get_class($subject), $method));
+ }
+
+ $marking = $subject->{$method}();
+
+ if (!$marking) {
+ return new Marking();
+ }
+
+ if ($this->singleState) {
+ $marking = [$marking => 1];
+ }
+
+ return new Marking($marking);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMarking($subject, Marking $marking, array $context = [])
+ {
+ $marking = $marking->getPlaces();
+
+ if ($this->singleState) {
+ $marking = key($marking);
+ }
+
+ $method = 'set'.ucfirst($this->property);
+
+ if (!method_exists($subject, $method)) {
+ throw new LogicException(sprintf('The method "%s::%s()" does not exists.', \get_class($subject), $method));
+ }
+
+ $subject->{$method}($marking, $context);
+ }
+}
diff --git a/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php
index 3ac0ff5827635..9e70614e02c34 100644
--- a/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php
+++ b/src/Symfony/Component/Workflow/MarkingStore/MultipleStateMarkingStore.php
@@ -46,7 +46,7 @@ public function getMarking($subject)
/**
* {@inheritdoc}
*/
- public function setMarking($subject, Marking $marking)
+ public function setMarking($subject, Marking $marking, array $context = [])
{
$this->propertyAccessor->setValue($subject, $this->property, $marking->getPlaces());
}
diff --git a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php
index 806b33340caa2..ca28017384ae9 100644
--- a/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php
+++ b/src/Symfony/Component/Workflow/MarkingStore/SingleStateMarkingStore.php
@@ -51,7 +51,7 @@ public function getMarking($subject)
/**
* {@inheritdoc}
*/
- public function setMarking($subject, Marking $marking)
+ public function setMarking($subject, Marking $marking, array $context = [])
{
$this->propertyAccessor->setValue($subject, $this->property, key($marking->getPlaces()));
}
diff --git a/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php
new file mode 100644
index 0000000000000..776f66002d23c
--- /dev/null
+++ b/src/Symfony/Component/Workflow/Tests/MarkingStore/MethodMarkingStoreTest.php
@@ -0,0 +1,74 @@
+getMarking($subject);
+
+ $this->assertInstanceOf(Marking::class, $marking);
+ $this->assertCount(0, $marking->getPlaces());
+
+ $marking->mark('first_place');
+
+ $markingStore->setMarking($subject, $marking);
+
+ $this->assertSame(['first_place' => 1], $subject->getMarking());
+
+ $marking2 = $markingStore->getMarking($subject);
+
+ $this->assertEquals($marking, $marking2);
+ }
+
+ public function testGetSetMarkingWithSingleState()
+ {
+ $subject = new Subject();
+
+ $markingStore = new MethodMarkingStore(true);
+
+ $marking = $markingStore->getMarking($subject);
+
+ $this->assertInstanceOf(Marking::class, $marking);
+ $this->assertCount(0, $marking->getPlaces());
+
+ $marking->mark('first_place');
+
+ $markingStore->setMarking($subject, $marking);
+
+ $this->assertSame('first_place', $subject->getMarking());
+
+ $marking2 = $markingStore->getMarking($subject);
+
+ $this->assertEquals($marking, $marking2);
+ }
+}
+
+final class Subject
+{
+ private $marking;
+
+ public function __construct($marking = null)
+ {
+ $this->marking = $marking;
+ }
+
+ public function getMarking()
+ {
+ return $this->marking;
+ }
+
+ public function setMarking($marking)
+ {
+ $this->marking = $marking;
+ }
+}
diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php
index ff229faabd314..7269defa2e11a 100644
--- a/src/Symfony/Component/Workflow/Workflow.php
+++ b/src/Symfony/Component/Workflow/Workflow.php
@@ -143,7 +143,7 @@ public function buildTransitionBlockerList($subject, string $transitionName): Tr
/**
* {@inheritdoc}
*/
- public function apply($subject, $transitionName)
+ public function apply($subject, $transitionName, array $context = [])
{
$marking = $this->getMarking($subject);
@@ -172,7 +172,7 @@ public function apply($subject, $transitionName)
$this->enter($subject, $transition, $marking);
- $this->markingStore->setMarking($subject, $marking);
+ $this->markingStore->setMarking($subject, $marking, $context);
$this->entered($subject, $transition, $marking);
diff --git a/src/Symfony/Component/Workflow/WorkflowInterface.php b/src/Symfony/Component/Workflow/WorkflowInterface.php
index 5a1f2c74e81aa..d6de18fee5794 100644
--- a/src/Symfony/Component/Workflow/WorkflowInterface.php
+++ b/src/Symfony/Component/Workflow/WorkflowInterface.php
@@ -58,7 +58,7 @@ public function buildTransitionBlockerList($subject, string $transitionName): Tr
*
* @throws LogicException If the transition is not applicable
*/
- public function apply($subject, $transitionName);
+ public function apply($subject, $transitionName, array $context = []);
/**
* Returns all enabled transitions.