diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
index 1766366252147..27ef7913e2c1f 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
@@ -244,6 +244,12 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
->arrayNode('audit_trail')
->canBeEnabled()
->end()
+ ->arrayNode('marking_history_store')
+ ->children()
+ ->scalarNode('history_property')->isRequired()->end()
+ ->scalarNode('memo_property')->end()
+ ->end()
+ ->end()
->enumNode('type')
->values(array('workflow', 'state_machine'))
->defaultValue('state_machine')
diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 1321d7126c899..a488e88d0b66a 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -572,6 +572,17 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
$markingStoreDefinition = new Reference($workflow['marking_store']['service']);
}
+ // create Marking History Store for optionally logging state history
+ if (isset($workflow['marking_history_store'])) {
+ $historyProperty = $workflow['marking_history_store']['history_property'];
+ $memoProperty = $workflow['marking_history_store']['memo_property'];
+
+ $markingHistoryDefinition = new Definition(Workflow\MarkingHistoryStore::class);
+ $markingHistoryDefinition->setPublic(false);
+ $markingHistoryDefinition->addArgument($historyProperty);
+ $markingHistoryDefinition->addArgument($memoProperty);
+ }
+
// Create Workflow
$workflowId = sprintf('%s.%s', $type, $name);
$workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type));
@@ -580,6 +591,9 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
$workflowDefinition->replaceArgument(1, $markingStoreDefinition);
}
$workflowDefinition->replaceArgument(3, $name);
+ if (isset($markingHistoryDefinition)) {
+ $workflowDefinition->replaceArgument(4, $markingHistoryDefinition);
+ }
// Store to container
$container->setDefinition($workflowId, $workflowDefinition);
diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
index d881f699f75ec..e0a2060c3babf 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml
@@ -12,17 +12,21 @@
null
+ null
null
+ null
+
+
diff --git a/src/Symfony/Component/Workflow/MarkingHistoryStore.php b/src/Symfony/Component/Workflow/MarkingHistoryStore.php
new file mode 100644
index 0000000000000..a638fafb6fde7
--- /dev/null
+++ b/src/Symfony/Component/Workflow/MarkingHistoryStore.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Workflow;
+
+use Symfony\Component\PropertyAccess\PropertyAccess;
+use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
+
+final class MarkingHistoryStore
+{
+ private $historyProperty;
+ private $memoProperty;
+
+ private $historyPropertyAccessor;
+ private $memoPropertyAccessor;
+
+ public function __construct(string $historyProperty, string $memoProperty = null, PropertyAccessorInterface $historyPropertyAccessor = null, PropertyAccessorInterface $memoPropertyAccessor = null)
+ {
+ $this->historyProperty = $historyProperty;
+ $this->memoProperty = $memoProperty;
+
+ $this->historyPropertyAccessor = $historyPropertyAccessor ?? PropertyAccess::createPropertyAccessor();
+ $this->memoPropertyAccessor = $memoPropertyAccessor ?? PropertyAccess::createPropertyAccessor();
+ }
+
+ /**
+ * @param $subject
+ * @param $transition Transition
+ * @param $marking Marking
+ * @param $workflowName string
+ */
+ public function updateMarkingHistory($subject, Transition $transition, Marking $marking, string $workflowName)
+ {
+ // get existing state history for this object
+ $existingHistory = $this->historyPropertyAccessor->getValue($subject, $this->historyProperty) ?? array();
+
+ // build the array to append to the log, using the workflow name as the log's key
+ $arr = array();
+ $arr['timestamp'] = date('Y-m-d H:i:s');
+ $arr['marking'] = $marking->getPlaces();
+ $arr['transition'] = $transition->getName();
+
+ if (null !== $this->memoProperty) {
+ $arr['memo'] = $this->memoPropertyAccessor->getValue($subject, $this->memoProperty) ?? ''; // an optional memo
+ }
+
+ // the key is used to allow logging the history of multiple workflows
+ $key = $workflowName;
+ if (!array_key_exists($key, $existingHistory)) {
+ $existingHistory[$key] = array();
+ }
+ $existingHistory[$key][] = $arr;
+
+ // set the history property value
+ $this->historyPropertyAccessor->setValue($subject, $this->historyProperty, $existingHistory);
+
+ // finally, clear out the memo field now that it's been logged
+ $this->memoPropertyAccessor->setValue($subject, $this->memoProperty, null);
+ }
+}
diff --git a/src/Symfony/Component/Workflow/StateMachine.php b/src/Symfony/Component/Workflow/StateMachine.php
index 6adf1f4dcbddb..7d5e7dfd56d99 100644
--- a/src/Symfony/Component/Workflow/StateMachine.php
+++ b/src/Symfony/Component/Workflow/StateMachine.php
@@ -1,5 +1,14 @@
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
namespace Symfony\Component\Workflow;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -11,8 +20,8 @@
*/
class StateMachine extends Workflow
{
- public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed')
+ public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', MarkingHistoryStore $markingHistoryStore = null)
{
- parent::__construct($definition, $markingStore ?: new SingleStateMarkingStore(), $dispatcher, $name);
+ parent::__construct($definition, $markingStore ?: new SingleStateMarkingStore(), $dispatcher, $name, $markingHistoryStore);
}
}
diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php
index 963f1b8b27180..bcfbca1e7b242 100644
--- a/src/Symfony/Component/Workflow/Workflow.php
+++ b/src/Symfony/Component/Workflow/Workflow.php
@@ -32,13 +32,15 @@ class Workflow implements WorkflowInterface
private $markingStore;
private $dispatcher;
private $name;
+ private $markingHistoryStore;
- public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed')
+ public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', MarkingHistoryStore $markingHistoryStore = null)
{
$this->definition = $definition;
$this->markingStore = $markingStore ?: new MultipleStateMarkingStore();
$this->dispatcher = $dispatcher;
$this->name = $name;
+ $this->markingHistoryStore = $markingHistoryStore;
}
/**
@@ -160,6 +162,10 @@ public function apply($subject, $transitionName)
$this->markingStore->setMarking($subject, $marking);
+ if (null !== $this->markingHistoryStore) {
+ $this->markingHistoryStore->updateMarkingHistory($subject, $transition, $marking, $this->getName());
+ }
+
$this->entered($subject, $transition, $marking);
$this->completed($subject, $transition, $marking);
@@ -363,4 +369,9 @@ private function announce($subject, Transition $initialTransition, Marking $mark
$this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event);
}
}
+
+ public function getMarkingHistoryStore(): ?MarkingHistoryStore
+ {
+ return $this->markingHistoryStore;
+ }
}
diff --git a/src/Symfony/Component/Workflow/WorkflowInterface.php b/src/Symfony/Component/Workflow/WorkflowInterface.php
index 5a1f2c74e81aa..a38379bb3f4be 100644
--- a/src/Symfony/Component/Workflow/WorkflowInterface.php
+++ b/src/Symfony/Component/Workflow/WorkflowInterface.php
@@ -85,4 +85,11 @@ public function getDefinition();
public function getMarkingStore();
public function getMetadataStore(): MetadataStoreInterface;
+
+ /**
+ * Returns the optional Marking History Store for logging the state history on the entity.
+ *
+ * @return null|MarkingHistoryStore
+ */
+ public function getMarkingHistoryStore(): ?MarkingHistoryStore;
}